Skip to content

Fix stream buffer double release, header recursion, and eager chunk allocation - #747

Merged
xerial merged 2 commits into
mainfrom
fix/pool-double-release-and-header-recursion
Oct 3, 2026
Merged

xerial merged 2 commits into
mainfrom
fix/pool-double-release-and-header-recursion

Conversation

@xerial

@xerial xerial commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the issues from the private security reports that 1.1.10.9 didn't cover.

  • Double release of pooled buffers in SnappyFramedInputStream. ensureBuffer() and allocateBuffersBasedOnSize() returned buffers to the pool before allocating their replacements. If the allocation threw (e.g. OutOfMemoryError), the fields still pointed at the released buffers, and close() released them again. CachingBufferPool doesn't detect duplicates, so two later streams could get the same backing array. The fields are now cleared immediately after release.
  • Unbounded recursion in SnappyInputStream.hasNextChunk(). Each header of a concatenated stream recursed once, so a long run of 16-byte headers overflowed the stack. Headers are now skipped in a loop, the same pattern as snappy-java through 1.1.10.8 Uncontrolled Recursion via SnappyFramedInputStream skippable chunks #731.
  • Eager chunk allocation in SnappyInputStream. The compressed-chunk buffer was allocated at the declared chunkSize (up to the 512 MiB MAX_CHUNK_SIZE) before any chunk data was read, so a truncated stream of a few bytes could force a 512 MiB allocation. The buffer now starts at 64 KiB, or the chunk size if smaller, and doubles as data arrives. The uncompressed-buffer allocation is already bounded by the size check added in Fix buffer overflows and unbounded allocations from untrusted input (CVE-2026-90559 and others) #739.
  • Raw memory-address APIs. rawCompress(long, long, long) and rawUncompress(long, long, long) take no destination capacity, so they can't be bounds-checked. Their Javadoc now marks them unsafe, says what the caller must check, and points to the bounds-checked ByteBuffer overloads.

Test plan

  • 3 new tests in SnappyBoundsCheckTest, all of which fail without the fix:
    • double release, using a pool that simulates OOM and asserts no buffer is released twice
    • 100,000 concatenated headers, which overflowed the stack before
    • a truncated chunk declaring 500 MiB, which must allocate under 100 MB
  • ./sbt test: 139 passed

🤖 Generated with Claude Code

xerial and others added 2 commits October 3, 2026 02:06
…llocation

- SnappyFramedInputStream: clear released buffer fields before allocating
  replacements, so close() does not release them to the pool again when
  the allocation fails, which let two streams share one buffer
- SnappyInputStream: skip concatenated stream headers in a loop instead
  of recursing once per 16-byte header
- SnappyInputStream: grow the compressed chunk buffer as data arrives
  instead of allocating the declared chunk size (up to 512 MiB) from a
  4-byte header
- Document that the raw memory address APIs cannot check bounds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the bug label Oct 3, 2026
@xerial
xerial merged commit 139a530 into main Oct 3, 2026
15 of 20 checks passed
@xerial
xerial deleted the fix/pool-double-release-and-header-recursion branch October 3, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant