Skip to content

test(mft): malformed-input corpus for cache deserializer (WI-5.3)#350

Merged
githubrobbi merged 1 commit into
mainfrom
harden/wi-5.3-malformed-tests
Jun 4, 2026
Merged

test(mft): malformed-input corpus for cache deserializer (WI-5.3)#350
githubrobbi merged 1 commit into
mainfrom
harden/wi-5.3-malformed-tests

Conversation

@githubrobbi

@githubrobbi githubrobbi commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

What & why

Completes WI-5.3 (Category 5: panic = DoS) of the "Bugs Rust Won't Catch" audit. The parser malformed-record test already landed in #349 (WI-5.2); this PR adds the cache-deserializer half.

The deserializer (index/storage/deserialize.rs) parses untrusted on-disk bytes — a persisted index reloaded at startup. The daemon runs panic = "abort", so a panic on a truncated/corrupt cache file is a whole-process DoS.

The deserializer was already hardened (returns Result<_, &'static str>, slices via .get(..).ok_or(..)?, sizes sections with checked_mul/checked_add). This PR is purely additive tests that lock that in — no production code change.

Added (crates/uffs-mft/src/index/tests_storage.rs)

  • valid-blob round-trip baseline (sanity for the mutation tests)
  • truncation sweep over a populated serialized index at every length
  • empty / 1-byte / single-0xFF rejection
  • oversized section-length header fields (u64::MAX in record-count / names-size / links-count) → clean error, no overflow/OOB
  • seeded deterministic fuzz loop (ChaCha8Rng::seed_from_u64, 5 000 iterations: bit-flips, random truncation, trailing garbage, fully random blobs)

Property under test

Livenessdeserialize returns Ok or Err but never panics/aborts — except where a specific rejection is provable (tiny inputs, a cut inside the fixed header, oversized length fields).

The truncation sweep deliberately does not assert "every prefix is an error": the deserializer is intentionally lenient about some trailing/optional sections, so a near-complete prefix may legitimately parse Ok (found empirically — a 1570/1610-byte prefix parsed). Asserting otherwise would encode a false contract; the test was corrected to match the real, safe behaviour.

No cargo-fuzz target added (would introduce a toolchain dependency without sign-off, per the plan's own caveat); the seeded ChaCha8Rng corpus gives deterministic, CI-friendly fuzz coverage instead.

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) — clean
  • cargo nextest run -p uffs-mft196 passed, 3 skipped (was 191; +5 deserializer tests)
  • full pre-commit + pre-push gates (fmt, file-size, typos, reuse, lint-ci, lint-prod, lint-tests, lint-ci-windows, cargo-vet) — ✅

With this, Category 5 is 100% (5.1 ✅, 5.2 ✅ via #349, 5.3 ✅).

Completes WI-5.3 (Category 5, "Bugs Rust Won't Catch"). The parser
malformed-record test landed with WI-5.2; this adds the cache-deserializer
half. The deserializer (index/storage/deserialize.rs) parses untrusted on-disk
bytes (a persisted index reloaded at startup) and the daemon runs
`panic = "abort"`, so a panic on a truncated/corrupt cache is a whole-process
DoS. It was already hardened (Result + .get().ok_or()? + checked_* sizing); this
is purely additive tests locking that in — no production code change.

Added to crates/uffs-mft/src/index/tests_storage.rs:
- valid-blob round-trip baseline (sanity for the mutation tests)
- truncation sweep over a *populated* serialized index at every length
- empty / 1-byte / single-0xFF rejection
- oversized section-length header fields (u64::MAX in record-count, names-size,
  links-count) -> clean error, no overflow/OOB
- seeded deterministic fuzz loop (ChaCha8Rng::seed_from_u64, 5000 iterations:
  bit-flips, random truncation, trailing garbage, fully random blobs)

The property under test is liveness: deserialize returns Ok or Err but never
panics. The truncation sweep does NOT assert "every prefix errors" — the
deserializer is intentionally lenient about trailing/optional sections, so a
near-complete prefix may legitimately parse Ok (found empirically at
1570/1610 bytes); a strict-error assertion is kept only for a cut inside the
fixed header. No cargo-fuzz target added (avoids a toolchain dep without
sign-off); the seeded ChaCha corpus gives deterministic CI fuzz coverage.

Verified: native + `cargo xwin` (Windows) `--all-targets -D warnings` clippy
clean; `cargo nextest run -p uffs-mft` 196 passed (was 191; +5 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@githubrobbi githubrobbi enabled auto-merge (squash) June 4, 2026 18:53
@githubrobbi githubrobbi merged commit 43215db into main Jun 4, 2026
21 checks passed
@githubrobbi githubrobbi deleted the harden/wi-5.3-malformed-tests branch June 4, 2026 19:05
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