Skip to content

fix(mft): harden all 5 NTFS parsers against panic-on-malformed-bytes (WI-5.2)#349

Merged
githubrobbi merged 1 commit into
mainfrom
harden/wi-5.2-parser-checked
Jun 4, 2026
Merged

fix(mft): harden all 5 NTFS parsers against panic-on-malformed-bytes (WI-5.2)#349
githubrobbi merged 1 commit into
mainfrom
harden/wi-5.2-parser-checked

Conversation

@githubrobbi

@githubrobbi githubrobbi commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

What & why

The daemon builds with panic = "abort", so any panic while parsing an untrusted on-disk MFT record is a whole-process DoS. The five NTFS parser entry points sliced untrusted data[a..b] and did raw offset + N / name_len * 2 arithmetic, suppressed by block-level #[expect(indexing_slicing)]. index.rs / index_extension.rs sit on the live USN-journal incremental-update path (usn/windows.rs), so this is reachable in production — not dead code.

This is WI-5.2 (Category 5: panic = DoS) of the "Bugs Rust Won't Catch" audit.

Changes

  • Every untrusted-data read now goes through .get() or bounds-safe rd_u16/rd_u32/rd_u64 helpers (return 0 on OOB).
  • Every byte-derived offset/length uses checked_add/checked_mul (or saturating_* where overflow is provably unreachable, with an inline justification comment). The classic name_len * 2 UTF-16 overflow is closed.
  • Behaviour preserved exactly — a malformed field yields the same skip/default the original if X+N <= len guards produced. No new error type; the parsers keep their bool/skip-bad-record contract (step-4 caller behaviour unchanged).
  • indexing_slicing expects narrowed, not removed — only internal arena indices remain (index.records[..], fragment.streams[..], frs_to_idx[..]), keyed by indices this code itself mints; each reason updated to say so.
  • #![warn(clippy::arithmetic_side_effects)] enabled module-scoped in all 5 parser files. Under the workspace -D warnings this is effectively deny inside the parsers — any new raw +/*/[..] on a byte-derived value fails the build there. (Workspace-wide deny is deliberately deferred: 1766 benign sites would hard-error; documented in the tracker.)
  • New regression test malformed_records_do_not_panic: a RecordBuilder forges 8 records that pass the header gate and runs them through all 3 live entry points, asserting no panic (offset-past-EOF, oversized length, name_len*2 overflow, non-resident size past EOF, reparse offset past EOF, zero-length attr, garbage body). This is the partial WI-5.3 deliverable; cache-deserializer fuzz remains a follow-up.

Files

crates/uffs-mft/src/io/parser/{unified,index,index_extension,fragment,fragment_extension,mod}.rs + tracker doc.
parse/merger.rs was checked and left unchanged (its indexing_slicing covers only internal vectors — 0 untrusted slices).

Verification

  • cargo clippy -p uffs-mft --all-targets --all-features -D warnings (native) — clean
  • cargo xwin clippy ... --target x86_64-pc-windows-msvc -D warnings (Windows cross, covers the cfg(windows) USN path) — clean
  • cargo nextest run -p uffs-mft191 passed, 3 skipped (was 190; +1 new test)
  • downstream uffs-core / uffs-cli build — clean
  • anti-pattern gate — no new violations (only the 4 known WI-4.2 sites remain, deferred)
  • full pre-commit + pre-push gates (fmt, file-size, typos, reuse, lint-ci/prod/tests, cargo-vet) — ✅

Note on behaviour parity

index_extension.rs uses existing_name_count + name_idx; the deprecated fragment_extension.rs uses the legacy existing_name_count - 1 + name_idx. Each file's existing formula was preserved verbatim (only +saturating_add). This divergence predates this PR; no semantic change is introduced.

…(WI-5.2)

The daemon builds with `panic = "abort"`, so any panic while parsing an
untrusted on-disk MFT record is a whole-process DoS. The five parser entry
points sliced untrusted `data[a..b]` and did raw `offset + N` / `name_len * 2`
arithmetic under block-level `#[expect(indexing_slicing)]`. `index.rs` /
`index_extension.rs` sit on the live USN-journal update path, so this is
reachable in production.

Changes (Category 5, "Bugs Rust Won't Catch" audit):
- Every untrusted-`data` read now goes through `.get()` or bounds-safe
  `rd_u16`/`rd_u32`/`rd_u64` helpers; every byte-derived offset/length uses
  `checked_add`/`checked_mul` (or `saturating_*` where overflow is provably
  unreachable, with inline justification). `name_len * 2` overflow closed.
- Behaviour preserved exactly: a malformed field yields the same skip/default
  the original `if X+N <= len` guards produced — no new error type, the parsers
  keep their `bool`/skip-bad-record contract.
- `indexing_slicing` expects narrowed (not removed): only internal arena indices
  remain (`index.records[..]`, `fragment.streams[..]`, `frs_to_idx[..]`), keyed
  by self-minted indices; reasons updated to say so.
- `#![warn(clippy::arithmetic_side_effects)]` enabled module-scoped in all 5
  parser files — under workspace `-D warnings` this is effectively deny inside
  the parsers, a regression guard against new raw arithmetic on disk bytes.
  (Workspace-wide deny deferred: 1766 benign sites would hard-error.)
- New regression test `malformed_records_do_not_panic`: a `RecordBuilder` forges
  8 records that pass the header gate, run through all 3 live parser entry
  points, asserting no panic (offset-past-EOF, oversized length, name_len*2
  overflow, non-resident size past EOF, reparse offset past EOF, zero-length
  attr, garbage body). Partial WI-5.3 (cache-deserializer fuzz still pending).

Verified: native + `cargo xwin` (Windows) `--all-targets -D warnings` clippy
clean; `cargo nextest run -p uffs-mft` 191 passed; downstream uffs-core/uffs-cli
build clean; anti-pattern gate shows no new violations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@githubrobbi githubrobbi enabled auto-merge (squash) June 4, 2026 18:27
@githubrobbi githubrobbi merged commit 202de88 into main Jun 4, 2026
21 checks passed
@githubrobbi githubrobbi deleted the harden/wi-5.2-parser-checked branch June 4, 2026 18:38
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