fix(doc): bound the LFO reservation by the records the table holds

SA
2d ago
+25-1

parse_plf_lfo (src/formats/doc/lists.rs) passes the PlfLfo's u32 count straight to Vec::with_capacity. A malformed .doc whose LFO table declares 0xFFFF_FFFF records but holds none asks for about 64 GB (16-byte (u32, usize) elements) before the loop finds that the records are missing. Where the allocator refuses, Rust aborts the process instead of panicking, so a caller can't recover. The loop already stops at the first missing record, so the reservation was the only unbounded step.

  • lfo_capacity clamps the reservation to (plf.len() - 4) / LFO_SIZE, the number of records that fit in the table.
  • The regression test asserts on that capacity rather than by allocating. On an overcommitting allocator (macOS, and Linux by default) the unbounded 64 GB reservation "succeeds", so a test that only parsed the table would pass even without the fix. I confirmed that this test fails with the bound removed and passes with it.

I checked the other with_capacity calls under src/. Each is bounded by a u16/u8 count or by the input's length; this was the only one taking a full u32 from the file.

cargo test --workspace (297 passed), cargo clippy --all-targets -- -D warnings and cargo fmt --check are all clean.

🤖 Generated with Claude Code


Summary by cubic

Fixes a parser DoS where a malformed .doc with an oversized LFO count could abort the process by requesting ~64 GB of memory up front.

  • lfo_capacity clamps the Vec reservation to the records the table can actually hold, matching the loop's existing stop-at-first-missing-record behavior.
  • Adds a regression test asserting on the reserved capacity, since overcommitting allocators would hide the bug in an allocation-based test.