Reject oversized four-byte literal length in snappy decoder - #801
Reject oversized four-byte literal length in snappy decoder#801kali834x wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Hardens the raw Snappy decoder against a crafted 4-byte literal-length field that previously could narrow to a zero-length literal and trigger unbounded recursion (stack overflow), by validating the 4-byte length before narrowing.
Changes:
- Read 4-byte literal-length values as a
longand reject values that would overflow a positiveintonce the Snappy+1adjustment is applied. - Add a regression test that exercises the
0xFFFFFFFF4-byte literal-length case and asserts aCompressorExceptionmentioning “literal length”.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/main/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStream.java | Adds an overflow/oversize guard for 4-byte literal lengths before converting to int. |
| src/test/java/org/apache/commons/compress/compressors/snappy/SnappyCompressorInputStreamTest.java | New test covering the oversized 4-byte literal-length edge case to prevent regression. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Switched to draft. There are many callers of the API that downcast the result to an int and therefore this issue needs a general solution, which coincidentally, I happen to be working on. |
|
Makes sense, a fix at the ByteUtils level covers all the call sites at once. Happy to rebase this onto your change once it lands, or close it if it's fully covered there. |
snappycompressorinputstream.readliterallength reads the four-byte literal length with byteutils.fromlittleendian and casts it straight to int, so a length field of 0xffffffff narrows to -1 and, after the +1 the method adds, becomes a zero-length literal. fill only guards against a negative literal size, so the zero-length literal slips through, consumes no input and emits no output, and read() then recurses into itself once per element until a crafted run of them overflows the stack with a stackoverflowerror that escapes the declared ioexception. reading the four-byte value as an unsigned long and rejecting anything that can't fit a positive int before narrowing keeps the check at the point of the conversion; the two- and three-byte forms can't overflow so they're untouched. reachable directly and through framedsnappycompressorinputstream, which decodes each chunk with this class.
mvn; that'smvnon the command line by itself.