librepgp: cap decompressed data and packet count during dump (fixes fuzz_dump OOM) - #2477
librepgp: cap decompressed data and packet count during dump (fixes fuzz_dump OOM)#2477ronaldtse wants to merge 3 commits into
Conversation
A crafted nested compressed data packet made the dump consume unbounded memory and time: each zlib layer expands up to ~1032x, and the JSON dump accumulated the whole decompressed packet stream in the DOM (fuzz_dump OOM at 2.1GB from a 4KB input, also reachable via rnp --list-packets on a crafted file). The existing MAXIMUM_NESTING_LEVEL and MAXIMUM_STREAM_PKTS checks did not help since they bound neither the decompressed size nor the number of non-streaming packets. Each dump call now shares a 256MB decompressed-data budget, enforced by a limiting source on top of every decompressed stream, and stops after 32768 packets. Reaching a limit truncates the dump gracefully.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2477 +/- ##
==========================================
- Coverage 85.47% 85.45% -0.02%
==========================================
Files 125 125
Lines 22964 23024 +60
==========================================
+ Hits 19628 19676 +48
- Misses 3336 3348 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The limiting source param held a shared_ptr which was released via free() instead of its destructor, leaking one reference (and with it the whole shared budget allocation) per compressed packet - caught by LeakSanitizer on the CI sanitizer legs. The parent dump context owns the budget and always outlives the limiter, so a raw pointer suffices.
ASAN on the slower CI runners needs over 50s for the 32768-packet dump of the bomb; unpatched the input never completes, so a generous bound still catches the regression.
|
@ni4 @antonsviridenko Ready for review -- all 137 checks green; the only red is the Summary of the fix:
ni4: your approval covers the first review; anton: would you take the second? Happy to walk through the limiting-source details - one subtlety worth noting is that the budget is shared across nesting levels precisely so 32-deep nesting cannot multiply it. |
|
@ronaldtse LGTM, thanks! Here we must have second approval as I understand. |
|
@antonsviridenko could you take the second review here when you have a moment? All 137 checks are green (only the known oss-fuzz The change is confined to the packet-dump layer: a shared 256MB decompressed-data budget (enforced by a limiting source around each decompressed stream, shared across nesting levels so 32-deep nesting cannot multiply it) plus a 32768-packet cap, so nested decompression bombs can no longer drive Two review notes that may save you time:
|
Summary
fuzz_dumpOOM that has made thefuzzing-opensslworkflow red since its first run (all 13 runs on main failed; oss-fuzz's Botan-basedfuzzingjob has the separate known Botan 3.4.0 pin issue, [rnp] Update Botan to 3.6.0 google/oss-fuzz#15882).rnp --list-packets(or the FFI dump calls) on a crafted file exhausts memory. The existingMAXIMUM_NESTING_LEVEL/MAXIMUM_STREAM_PKTSchecks bound neither the decompressed size nor the count of non-streaming packets (the observed bomb used pk-session-key packets).:too much decompressed data, stopping./:too many packets, stopping.), consistent with the existing "too many layers" behavior.test_fuzz_dumpcorpus (outofmemory-12fd9b02d8465f2f, identical to the one libFuzzer saved on CI) with a 30s completion bound; it completes in ~1.5s.test_fuzz_dump,test_stream_dumper,test_ffi_pkt_dump,test_cli_dump,test_ffi_dump_multiple_armored_messages, key dumps, etc.).Security impact
Availability-only (CWE-409 / CWE-400): crafted input to
--list-packets/ dump FFI causes memory exhaustion. Happy to file it through the security channel instead if preferred - flagging here for visibility.Test plan
fuzzing-opensslworkflow passes on this PR (first time ever)