Skip to content

snappy: fix OOB reads in framed data parser#11855

Merged
edsiper merged 2 commits into
fluent:masterfrom
TristanInSec:fix/snappy-frame-parsing
Jun 2, 2026
Merged

snappy: fix OOB reads in framed data parser#11855
edsiper merged 2 commits into
fluent:masterfrom
TristanInSec:fix/snappy-frame-parsing

Conversation

@TristanInSec
Copy link
Copy Markdown
Contributor

@TristanInSec TristanInSec commented May 26, 2026

The snappy framed data parser has two issues:

  1. The loop reads a 4-byte frame header without checking that 4 bytes
    are available. When 1-3 bytes remain after the last valid frame,
    the uint32_t read at frame_buffer[1] extends past the buffer.
    Also missing a check that the full frame body (offset + 4 +
    frame_length) fits within the input.

  2. Both compressed and uncompressed data paths subtract sizeof(uint32_t)
    from frame_length for the checksum without checking frame_length >= 4.
    When frame_length < 4, the subtraction wraps to a huge size_t value
    on 64-bit, causing flb_snappy_uncompress() or memcpy() to read far
    past the buffer.

Add bounds checks for the frame header read, total frame extent, and
minimum frame_length before the checksum subtraction.

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation when reading framed compressed data to avoid crashes or incorrect behavior with malformed or truncated inputs; the parser now detects incomplete or undersized frames (including missing checksum/header) and stops parsing, returning an error to prevent unsafe reads and improve stability when handling compressed payloads.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4bc28e9-16b1-4c3f-8616-205eb84be9d1

📥 Commits

Reviewing files that changed from the base of the PR and between eef89da and f880d49.

📒 Files selected for processing (1)
  • src/flb_snappy.c

📝 Walkthrough

Walkthrough

flb_snappy_uncompress_framed_data() adds defensive bounds checks: the loop ensures enough bytes exist for frame headers and declared payloads, and COMPRESSED_DATA / UNCOMPRESSED_DATA frames validate that their frame_length includes the 4-byte checksum field.

Changes

Snappy Framed Data Bounds Checking

Layer / File(s) Summary
Frame loop bounds validation
src/flb_snappy.c
The framed-data iteration loop validates offset + 4 <= in_len before reading the frame header.
Frame payload length and max-size validation
src/flb_snappy.c
Validates offset + 4 + frame_length <= in_len and rejects frames that exceed the configured maximum frame size, setting result = -2 to stop parsing.
Frame checksum minimum-length validation
src/flb_snappy.c
Both COMPRESSED_DATA and UNCOMPRESSED_DATA handlers validate frame_length >= sizeof(uint32_t) to ensure the 4-byte checksum field exists; parsing sets result = -2 and stops when violated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested labels

backport to v4.0.x, backport to v4.1.x

Suggested reviewers

  • cosmo0920

Poem

🐰 I nibble bytes with careful paws,
Counting headers, lengths, and laws.
Four-byte guards keep dangers far,
No stray frame slips past my bar.
Snappy and safe — hop, hop, hoorah!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'snappy: fix OOB reads in framed data parser' accurately and concisely summarizes the main change: fixing out-of-bounds read vulnerabilities in the snappy framed data parser.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/flb_snappy.c (1)

169-179: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Off-by-one in frame header bounds check: uint32_t read at frame_buffer[1] accesses offset+4.

The check at line 169 ensures offset + 4 <= in_len (indices 0–3 valid). However, line 178 reads a uint32_t at &frame_buffer[1], accessing bytes at indices 1, 2, 3, 4. When exactly 4 bytes remain (offset + 4 == in_len), index 4 is out of bounds.

This affects valid trailing 4-byte frames (e.g., empty PADDING). Rather than requiring 5 bytes and rejecting valid frames, read only the 3 bytes actually needed:

🔒 Proposed fix: read frame_length as 3 bytes
         frame_type    = *((uint8_t *) &frame_buffer[0]);
 
-        frame_length  = *((uint32_t *) &frame_buffer[1]);
-        frame_length &= 0x00FFFFFF;
+        frame_length  = ((uint32_t)((unsigned char)frame_buffer[1]))       |
+                        ((uint32_t)((unsigned char)frame_buffer[2]) << 8)  |
+                        ((uint32_t)((unsigned char)frame_buffer[3]) << 16);
 
         frame_body    = &frame_buffer[4];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/flb_snappy.c` around lines 169 - 179, The bounds check is correct for a
4-byte header (bytes 0–3) but the code incorrectly reads a uint32_t from
&frame_buffer[1], which touches byte 4; change the frame length read to assemble
only the 3 bytes at frame_buffer[1..3] instead of dereferencing a uint32_t
pointer. Concretely, keep frame_type = frame_buffer[0], then compute
frame_length = ((uint32_t)frame_buffer[1]) | ((uint32_t)frame_buffer[2] << 8) |
((uint32_t)frame_buffer[3] << 16) (and remove the subsequent &0x00FFFFFF mask),
so you no longer perform an out-of-bounds/unaligned 32-bit read on frame_buffer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/flb_snappy.c`:
- Around line 169-179: The bounds check is correct for a 4-byte header (bytes
0–3) but the code incorrectly reads a uint32_t from &frame_buffer[1], which
touches byte 4; change the frame length read to assemble only the 3 bytes at
frame_buffer[1..3] instead of dereferencing a uint32_t pointer. Concretely, keep
frame_type = frame_buffer[0], then compute frame_length =
((uint32_t)frame_buffer[1]) | ((uint32_t)frame_buffer[2] << 8) |
((uint32_t)frame_buffer[3] << 16) (and remove the subsequent &0x00FFFFFF mask),
so you no longer perform an out-of-bounds/unaligned 32-bit read on frame_buffer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9068ef26-5ed9-42eb-a2e8-b04d68e2594c

📥 Commits

Reviewing files that changed from the base of the PR and between f6126eb and 4d15a10.

📒 Files selected for processing (1)
  • src/flb_snappy.c

@edsiper
Copy link
Copy Markdown
Member

edsiper commented May 27, 2026

@TristanInSec would you please sign off the commits ? (DCO error / git commit -s ...)

@edsiper edsiper added this to the Fluent Bit v5.0.7 milestone May 27, 2026
The snappy framed data parser has two issues:

1. The loop reads a 4-byte frame header without checking that 4 bytes
   are available.  When 1-3 bytes remain after the last valid frame,
   the uint32_t read at frame_buffer[1] extends past the buffer.
   Also missing a check that the full frame body (offset + 4 +
   frame_length) fits within the input.

2. Both compressed and uncompressed data paths subtract sizeof(uint32_t)
   from frame_length for the checksum without checking frame_length >= 4.
   When frame_length < 4, the subtraction wraps to a huge size_t value
   on 64-bit, causing flb_snappy_uncompress() or memcpy() to read far
   past the buffer.

Add bounds checks for the frame header read, total frame extent, and
minimum frame_length before the checksum subtraction.

Signed-off-by: Tristan <tristan@talencesecurity.com>
@TristanInSec TristanInSec force-pushed the fix/snappy-frame-parsing branch from 4d15a10 to eef89da Compare May 27, 2026 21:56
The frame header is 4 bytes: 1 byte type + 3 bytes little-endian
length. The previous code read a uint32_t at frame_buffer[1] which
touches byte index 4, but the bounds check only ensures indices 0-3
are valid. When exactly 4 bytes remain, byte 4 is out of bounds.

Read the 3-byte length field byte-by-byte instead. This also fixes
a potential unaligned memory access on strict-alignment architectures.

Signed-off-by: Tristan <tristan@talencesecurity.com>
@edsiper edsiper merged commit b7ed480 into fluent:master Jun 2, 2026
48 of 50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants