Skip to content

cfb: fix panic and unbounded allocation (OOM) on malformed compound files - #762

Open
1337-L3V1ATH0N wants to merge 1 commit into
tafia:masterfrom
1337-L3V1ATH0N:fix-cfb-dos
Open

1337-L3V1ATH0N wants to merge 1 commit into
tafia:masterfrom
1337-L3V1ATH0N:fix-cfb-dos

Conversation

@1337-L3V1ATH0N

@1337-L3V1ATH0N 1337-L3V1ATH0N commented Oct 28, 2026 •

Copy link
Copy Markdown

Summary

Two denial-of-service bugs in the CFB/OLE2 parser (src/cfb.rs), reachable via the public reader API (open_workbook, Xls::new, Xlsb::new; also VBA extraction in .xlsx) on crafted input. DoS only — no memory unsafety (safe Rust turns the out-of-bounds read into a bounds-checked panic). Found by fuzzing the .xls path (the existing fuzz_all harness only covers .xlsx). Both reproduce on the current release (0.37.0).

1. Panic — out-of-bounds slice index in Directory::from_slice

The directory stream is parsed in .chunks(128), but fixed offsets (buf[..64], buf[116..120], buf[120..128]) are read without checking the chunk is 128 bytes, so a stream length that isn't a multiple of 128 yields a short final chunk and panics (range end index 64 out of range for slice of length N). SectorSize::Small 64-byte mini-sectors are a second trigger. Fix: skip chunks that aren't a full 128-byte entry.

2. Unbounded allocation / OOM in the DIFAT loader

A crafted self-referential DIFAT chain grows difat without bounding iterations (the code notes //TODO: check if in infinite loop); a 512-byte file can drive a multi-GB allocation. Fix: bound the chain to maxid sectors and error out, and replace difat.pop().unwrap() with a checked error.

Tests

Adds two regression fixtures + tests (cfb_short_directory_chunk_does_not_panic, cfb_difat_chain_does_not_oom). cargo test passes; both fail (panic / OOM) without the fix.

Disclosure / CVE

These are reachable by any consumer that parses untrusted spreadsheets. I'm filing a RustSec advisory and will request a CVE; happy to coordinate on a patched release and a GitHub Security Advisory. Minimal PoCs are included as the test fixtures (and available separately on request).

@1337-L3V1ATH0N

Copy link
Copy Markdown
Author

For coordinated disclosure: I've filed a RustSec advisory for these two DoS issues — rustsec/advisory-db#3457.

Since this is a security fix, would you be open to either enabling private vulnerability reporting (Settings → Code security) or publishing a GitHub Security Advisory for it? That would allow a CVE to be assigned and linked to the fix. I'm happy to provide whatever the advisory needs (minimal PoC .xls/.xlsb files, CVSS, CWEs). Thanks for maintaining calamine!

The OLE2/CFB parser (used by .xls, .xlsb, and VBA extraction in .xlsx) could
panic or allocate without bound on crafted input, reachable via the public
reader API (open_workbook / Xls::new / Xlsb::new):

- Directory::from_slice read fixed offsets (0..64, 116..120, 120..128) from
  `.chunks(128)` entries without checking the chunk length, panicking on a short
  final chunk ("range end index 64 out of range for slice of length N"). Skip
  chunks that are not a full 128-byte entry.

- The DIFAT loader grew `difat` without bounding iterations (a self-referential
  chain), so a 512-byte file could drive a multi-GB allocation (OOM). Bound the
  chain to `maxid` sectors and error out; also replace difat.pop().unwrap() with
  a checked error.

Both are denial-of-service only (safe Rust; no memory unsafety). Adds regression
tests and fixtures.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

2 participants