{"record":{"id":"6c559c5f582f72c4","repo":"OpenNHP/opennhp","slug":"errdatadecompressionfailed","errorCode":"ErrDataDecompressionFailed","errorMessage":"decompressed size %d exceeds limit %d","messagePattern":"decompressed size (.+?) exceeds limit (.+?)","errorType":"error_code","errorClass":"ErrDataDecompressionFailed","httpStatus":null,"severity":"error","filePath":"nhp/core/responder.go","lineNumber":690,"sourceCode":"\t\tif err != nil {\n\t\t\tlog.Critical(\"invalid compressed data: %v\", err)\n\t\t\tErrDataDecompressionFailed.SetExtraError(err)\n\t\t\treturn ErrDataDecompressionFailed\n\t\t}\n\t\tdefer r.Close()\n\n\t\t// Limit decompressed size to 10MB to prevent DoS via decompression bomb\n\t\tconst maxDecompressedSize = 10 * 1024 * 1024\n\t\tlimitedReader := io.LimitReader(r, maxDecompressedSize+1) // +1 to detect overflow\n\t\tn, err := io.Copy(&buf, limitedReader)\n\t\tif err != nil {\n\t\t\tlog.Critical(\"message decompression failed: %v\", err)\n\t\t\tErrDataDecompressionFailed.SetExtraError(err)\n\t\t\treturn ErrDataDecompressionFailed\n\t\t}\n\t\tif n > maxDecompressedSize {\n\t\t\tlog.Critical(\"decompressed data exceeds maximum size limit (%d bytes)\", maxDecompressedSize)\n\t\t\tErrDataDecompressionFailed.SetExtraError(fmt.Errorf(\"decompressed size %d exceeds limit %d\", n, maxDecompressedSize))\n\t\t\treturn ErrDataDecompressionFailed\n\t\t}\n\n\t\tppd.BodyMessage = buf.Bytes() // separately allocated memory\n\t\t//log.Debug(\"message decompressed %v -> %v\", body, ppd.BodyMessage)\n\t} else {\n\t\tppd.BodyMessage = append(ppd.BodyMessage, body...) // deep copy\n\t}\n\n\treturn nil\n}\n\nfunc (ppd *PacketParserData) sendCookie() {\n\t// Only NHP_SERVER reaches this path (the call site in validatePeer is\n\t// gated on deviceType == NHP_SERVER). Server startup guarantees a\n\t// signing key is installed — either operator-supplied for clusters,\n\t// or randomly generated at process start for single-instance\n\t// deployments — so an empty key here is a programmer error.","sourceCodeStart":672,"sourceCodeEnd":708,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/responder.go#L672-L708","documentation":"In decryptBody, after successfully decompressing the packet body, the responder checks the decompressed output size n against maxDecompressedSize. If the output exceeds the limit, it logs a critical message, attaches the size detail to ErrDataDecompressionFailed, and returns that sentinel error. This defends against decompression bombs — a small malicious compressed body that would expand to exhaust responder memory.","triggerScenarios":"A received NHP packet body that is flagged as compressed decompresses to more than maxDecompressedSize bytes: either an attacker sending a crafted decompression bomb, or a legitimate peer compressing an oversized message (huge transaction payload, oversized plugin/auth data) that exceeds the server's configured limit.","commonSituations":"DoS attempts using gzip/flate bombs against the responder, clients transmitting very large auth or transaction messages without chunking, mismatched maxDecompressedSize configuration between peers after a version upgrade that raised the limit on one side only.","solutions":["Reduce the message size on the sending side — chunk or split the payload into multiple NHP messages so each decompressed body fits the limit.","If the traffic is trusted and legitimately large, raise maxDecompressedSize in the responder configuration and ensure adequate memory headroom.","Verify both peers run compatible versions with matching compression/size-limit settings.","If the packet came from an unknown source, treat it as an attack: the error already returns ErrDataDecompressionFailed; check logs (log.Critical) for the source address and block it."],"exampleFix":"// before: sender packs an entire huge payload into one message\nsendNhpMessage(compress(entireDataset)) // decompressed > maxDecompressedSize\n\n// after: chunk the payload\nfor chunk := range split(entireDataset, maxChunkSize) {\n    sendNhpMessage(compress(chunk))\n}","handlingStrategy":"try-catch","validationCode":"// client-side: check uncompressed size before compressing/sending\nconst maxDecompressedSize = 1 << 20 // match responder limit\nif len(body) > maxDecompressedSize {\n    return fmt.Errorf(\"payload %d exceeds limit %d; chunk it before sending\", len(body), maxDecompressedSize)\n}\ncompressed := compress(body)","typeGuard":null,"tryCatchPattern":"if errors.Is(err, nhpcore.ErrDataDecompressionFailed) {\n    // decompressed body exceeded maxDecompressedSize (or failed to decompress)\n    log.Printf(\"oversized/invalid compressed body from %s: %v\", srcAddr, err)\n    // drop connection / block source; do not retry same payload\n}","preventionTips":["Chunk large payloads client-side so each message decompresses under the limit","Keep maxDecompressedSize configuration consistent across peer versions","Cap message size at the application layer before compression","Treat repeated decompression-limit hits from one address as a decompression-bomb attack and block the source"],"tags":["decompression-bomb","payload-too-large","security","dos"],"backgroundTag":"payload-too-large","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}