Skip to content

Bound the compressed length of oversized LZ4 blocks - #162

Merged
yawkat merged 1 commit into
mainfrom
harden/block-input-stream-oversized-bound
Oct 15, 2026
Merged

yawkat merged 1 commit into
mainfrom
harden/block-input-stream-oversized-bound

Conversation

@yawkat

@yawkat yawkat commented Oct 15, 2026

Copy link
Copy Markdown
Owner

This is a hardening change, not a bug fix.

With withAcceptOversizedBlocks(true), LZ4BlockInputStream accepts LZ4-method blocks whose compressed length is at least the original length. It grows compressedBuffer to the header's compressedLen before reading the payload, and the only upper bound is Integer.MAX_VALUE. GHSA-4v53-57pg-c464 and the javadoc of withAcceptOversizedBlocks already document this trade-off.

The opt-in mode doesn't need an unbounded value. A legitimate LZ4 block can't be larger than the LZ4 compress bound of its original length (LZ4Utils.maxCompressedLength(originalLen), i.e. LZ4_compressBound). originalLen is already capped by the block size in the header (at most 32 MiB). This PR rejects LZ4 blocks whose compressedLen exceeds that bound, in all modes, with the same Stream is corrupted error. Even with oversized blocks enabled, the compressed-buffer allocation is now at most slightly more than the declared block size.

Details:

  • In the default mode this check never fires, because compressedLen < originalLen is already enforced.
  • RAW blocks already require compressedLen == originalLen, so they need no extra check.
  • The noncanonical blocks the option exists for (originalLen <= compressedLen <= bound) are still accepted. The existing testAcceptsInvalidLz4CompressedLengthInOversizedBlocksMode still passes.
  • I updated the javadoc on the class and on Builder.withAcceptOversizedBlocks.

This came up while fuzzing with micronaut-fuzzing.

Test: I added LZ4BlockStreamingTest.testRejectsLz4CompressedLengthAboveCompressBoundInOversizedBlocksMode. It checks that a header with compressedLen of maxCompressedLength(16) + 1 or Integer.MAX_VALUE is rejected in oversized mode before the payload is read. Without the change it fails: the stream tries to read the payload and reports Stream ended prematurely. With the change, LZ4BlockStreamingTest passes.

🤖 Generated with Claude Code

With acceptOversizedBlocks enabled, LZ4BlockInputStream allocated the
compressed buffer for the compressedLen from the block header with no
upper bound other than Integer.MAX_VALUE. An LZ4 block cannot be larger
than the LZ4 compress bound of its original length, so reject LZ4 blocks
whose compressed length exceeds LZ4Utils.maxCompressedLength(originalLen)
in all modes. This limits the allocation in the opt-in mode to slightly
more than the original length, which is itself limited by the block size
in the header. RAW blocks already require compressedLen == originalLen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yawkat
yawkat marked this pull request as ready for review October 15, 2026 13:23
@yawkat
yawkat merged commit f83ee9c into main Oct 15, 2026
3 checks passed
@yawkat
yawkat deleted the harden/block-input-stream-oversized-bound branch October 15, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant