test: cargo-fuzz harnesses for the decode boundary + bitrot guard + fuzzing guide - #1106
Conversation
Adds eight fuzz targets over the code that turns attacker-influenceable bytes
into in-memory structures — the sharpest trust boundary in the tree:
graph_from_bytes Graph::from_bytes (remote-execution wire format)
graph_roundtrip structure-aware from_bytes(to_bytes(g)) == g
remote_index_from_reader IndexFile::from_reader (index.shisha) [Linux only]
spec_hash_from_hex SpecHash::from_hex
target_from_str Target::from_str
mfile_from_toml minimal.toml through the custom serde visitors
arg_schema_parse ArgSchema::try_from
jq_parse_json jq::parse_file, JSON branch
Each fuzz/ dir is its own workspace so the nightly + sanitizer build cannot
perturb the main one. Three small production hooks are needed:
* graph: `Graph::fuzz_roundtrip` behind a new off-by-default `fuzzing`
feature (graphs are not hand-constructible from outside the crate), and
`insert_build` gated on `any(test, feature = "fuzzing")` rather than test
alone. Default builds are unchanged.
* mfile: `File::from_toml_bytes`, the pure filesystem-free core of
`from_dir`, so the harness need not re-implement it.
* workspace: `arbitrary`, optional and only enabled by graph's `fuzzing`.
Also carries the graph corpus seeds; a valid graph-with-local-file seed is
what let the fuzzer reach the local-file frame decoder.
This is the tooling behind the six decoder fixes already merged in #651,
#653, #656, #661 and #693.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each fuzz/ dir declares its own [workspace], so no workspace-wide build ever compiles the fuzz targets and they rot silently as the crates they fuzz evolve. The first version of this suite went 253 commits before anyone rebuilt it, by which point a target referenced a renamed type. `just fuzz-check` is the guard: a plain `cargo check` over every fuzz workspace. No nightly, no sanitizer, no libFuzzer runtime — just "does this still compile against today's API", so it runs anywhere and is cheap enough to treat like a red build. `just fuzz <crate> <target> [args]` runs one target, applying the RSS cap that turns an unbounded-allocation bug into a catchable crash rather than an ambient OOM. The rcache target is excluded on macOS (rcache -> lcache -> the Linux-only common::renameat2), following the existing `scope` idiom. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers prerequisites, the eight targets and their trust levels, running via the just recipes, corpus seeding (and why seeding is what unlocks the deep decode paths), reproducing and minimizing a crash, keeping targets from bitrotting, the bugs the campaign found, and where to take it next. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (26)
Comment |
Adds the cargo-fuzz suite for minimal's untrusted-decode boundary — the code that turns attacker-influenceable bytes into in-memory structures — plus a bitrot guard and
docs/fuzzing.mdso the campaign is pick-up-able.This is the tooling behind six decoder bugs already fixed and merged: #651 (tar path traversal → arbitrary write), #653 (two allocation OOMs in the graph wire decoder), #656 (out-of-bounds slice panic + filename traversal), #661 (
jqpanic on malformed JSON), #693 (unreachable!()on a Subset build-dep).The eight targets
graph_from_bytesGraph::from_bytes— remote-execution wire formatgraph_roundtripfrom_bytes(to_bytes(g)) == gremote_index_from_readerIndexFile::from_reader—index.shishaspec_hash_from_hexSpecHash::from_hextarget_from_strTarget::from_strmfile_from_tomlminimal.tomlvia the custom serde visitorsarg_schema_parseArgSchema::try_fromjq_parse_jsonjq::parse_file, JSON branchremote_index_from_readeris Linux-only becausercache→lcache→ the Linux-onlycommon::renameat2.Production-code surface is deliberately tiny
Everything else is new files under
crates/*/fuzz/. Default builds are byte-for-byte unchanged — thefuzzingfeature is off and the fuzz workspaces are excluded from the root members.crates/graph/src/graph.rsGraph::fuzz_roundtripbehind a new off-by-defaultfuzzingfeature;insert_buildgated onany(test, feature = "fuzzing")instead oftestalonecrates/graph/Cargo.toml, rootCargo.tomlarbitrary, enabled only by that featurecrates/mfile/src/lib.rsFile::from_toml_bytes— the pure, filesystem-free core offrom_dirGraphs aren't hand-constructible from outside the crate, hence the in-crate
fuzz_roundtripentry point rather than widening real public API.just fuzz-check— the bitrot guardEach
fuzz/dir is its own workspace so the nightly + sanitizer build can't perturb the main one. The cost: no workspace-wide build ever compiles these targets, so they rot silently. Not hypothetical — this suite sat 253 commits before being rebuilt, by which point one target referenced a renamed type (RemoteIndex→IndexFile, #689).just fuzz-checkruns a plaincargo checkover every fuzz workspace: stable, no nightly, no sanitizer — just "does this still compile against today's API." Cheap enough to treat like a red build.just fuzz <crate> <target> [libfuzzer args]runs one target and applies the-rss_limit_mbcap that turns an unbounded-allocation bug into a catchable crash rather than an ambient OOM..github/workflows/is frozen and CODEOWNER-gated, so per the test-extension contract in CONTRIBUTING.md this integrates through the justfile — following themiri/msrvprecedent (opt-in recipes, not wired intojust ci). If maintainers want it gated,fuzz-checkis the recipe to add to a lane.Validation
just fuzz-check— all four macOS-buildable fuzz workspaces check clean against current main.cargo build -p graph -p mfile(feature off) andcargo build -p graph --features fuzzingboth clean.cargo clippy -p graph -p mfile --all-targets -- -D warnings, and again with--features fuzzing— clean.cargo fmt --all -- --check— clean.spec_hasherhash-assertion tests fail on macOS (arch-pinned expected values). Verified identical failures with this branch'sgraph.rschange stashed — not introduced here.Follow-ups (not in this PR)
remote_index_from_readeron Linux — it has never been executed, only compiled.lcache::ReadTracker, theminimald-rpcrequest enums, and thepaths/sessionscontainment gates.🤖 Generated with Claude Code
Note
Add cargo-fuzz harnesses for decode boundary testing across multiple crates
cargo-fuzzworkspaces forargs,common,graph,mfile, andrcachecrates, each with one or more fuzz targets covering parsing and decode entry points.Graph::fuzz_roundtrip(behind afuzzingfeature flag) in crates/graph/src/graph.rs that encodes and decodes a generatedGraphand asserts invariants, panicking on mismatch.File::from_toml_bytesto crates/mfile/src/lib.rs as a public API for parsing TOML content from raw bytes.fuzz-checkandfuzzrecipes to the justfile to catch bitrot and run targets with nightly under an RSS limit.Macroscope summarized 0ac7bf8.