Skip to content

fix: bound decodeString/decodeBytes growth to avoid OOM on untrusted length#4

Open
moshap-firebolt wants to merge 1 commit into
mainfrom
moshap/decode-length-oom-guard
Open

fix: bound decodeString/decodeBytes growth to avoid OOM on untrusted length#4
moshap-firebolt wants to merge 1 commit into
mainfrom
moshap/decode-length-oom-guard

Conversation

@moshap-firebolt

@moshap-firebolt moshap-firebolt commented Jul 3, 2026

Copy link
Copy Markdown

Problem

BinaryDecoder::decodeString / decodeBytes read a length prefix and then
resize() the destination to that full length in a single call before reading
any bytes. The length prefix is attacker-controlled when the Avro data is
untrusted (e.g. Iceberg manifest files read by PackDB). A malformed multi-GiB
length triggers a huge allocation — an OOM — before the decoder discovers the
stream is far shorter than the declared length.

Surfaced by PackDB's iceberg_avro libFuzzer harness: a mutated length prefix
in the Avro file's metadata map (map<string,bytes>) drove a ~2 GiB
std::string::resize in readHeader(), reported as a libFuzzer OOM.

Fix

Grow the destination in bounded 1 MiB chunks. A bogus length now fails on the
"EOF reached" exception thrown by readBytes() after only a bounded
allocation, while well-formed values still read in full (identical bytes).

Test

  • Reproduced the OOM against the PackDB iceberg_avro fuzzer, then confirmed
    the crash input runs clean (exit 0, 2 ms) with this change.
  • Short smoke-fuzz produced no new crashes and no decoding regression.

…length

Avro string/bytes values carry a length prefix that is attacker controlled
when the Avro data is untrusted (e.g. Iceberg manifest files). decodeString
and decodeBytes resized their destination to that full length in one call,
so a malformed multi-GiB length triggered a huge allocation (OOM) before a
single byte was read and the stream was found to be far shorter.

Grow the destination in bounded 1 MiB chunks instead: a bogus length now
fails on the "EOF reached" thrown by readBytes() after only a bounded
allocation, while well-formed values still read in full.
@moshap-firebolt

Copy link
Copy Markdown
Author

Review pass: this change looks correct to me. The bounded 1 MiB chunk growth in decodeString/decodeBytes prevents attacker-controlled OOM from oversized length prefixes while still streaming valid payloads correctly. I also checked that all PR checks are passing.

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