feat: feature-gated Arbitrary derives for structure-aware fuzzing - #45
Closed
stormer78 wants to merge 1 commit into
Closed
feat: feature-gated Arbitrary derives for structure-aware fuzzing#45stormer78 wants to merge 1 commit into
stormer78 wants to merge 1 commit into
Conversation
Adds an off-by-default `arbitrary` feature that derives `arbitrary::Arbitrary` on the public log-entry / parameters graph (`LogEntry`, `LogEntry1_0`, `LogEntry1_0Pre`, `Parameters`, `Parameters1_0`, `Parameters1_0Pre`, `Version`, `Multibase`, `Witnesses`, `Witness`). This lets downstream consumers run structure-aware, coverage-guided fuzzing (cargo-fuzz) of the chain verifier instead of byte mutation, which almost never survives JSON parsing and so rarely reaches `DIDWebVHState::validate()`. Three fields on the log-entry structs have types from foreign crates that can't derive `Arbitrary`, so they use `#[arbitrary(with = ...)]` generators in the new `arbitrary_support` module: - `version_time: DateTime<FixedOffset>` — bounded Unix timestamp. - `state: serde_json::Value` — bounded recursive JSON. - `proof: Vec<DataIntegrityProof>` — emitted empty (a valid proof must be signed over the canonical bytes after the entry is built; harnesses that want to exercise signature verification should sign the generated entry or mutate a seed-corpus entry that already carries proofs). The feature adds no always-on dependency and is fully gated via `#[cfg_attr(feature = "arbitrary", ...)]`, so default builds are unchanged. `examples/fuzz_validate.rs` (gated on the feature) shows a ready-to-copy libfuzzer target body driving the verifier. Closes #44
Collaborator
Author
|
Superseded by #46 (shipped in v0.5.5), which is more complete than this PR — it generates real |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an off-by-default
arbitraryfeature that derivesarbitrary::Arbitraryon the public log-entry / parameters graph, so downstream consumers can run structure-aware, coverage-guided fuzzing (cargo-fuzz) of the chain verifier.Closes #44. Requested by the affinidi-webvh-service hosting service, which fuzzes the publish path that bottoms out in
LogEntry::deserialize_string+DIDWebVHState::validate()(their tracking issue: affinidi/affinidi-webvh-service#45).Why
Raw byte/string mutation almost never produces input that survives
LogEntrydeserialization, let alone a multi-entry chain whose hashes link — so a fuzzer spends ~all its budget bouncing off the JSON parser and rarely reachesvalidate(), where the interesting bugs live (SCID derivation, entry-hash linkage, parameter transitions, pre-rotation, deactivation tamper detection). WithArbitrary, a harness turns a&[u8]into a structurally-valid-but-mutated chain and drives the verifier directly.Types covered
LogEntry,LogEntry1_0,LogEntry1_0Pre,Parameters,Parameters1_0,Parameters1_0Pre,Version,Multibase,Witnesses,Witness.Foreign-type fields
Three fields on the log-entry structs have types from foreign crates with no
Arbitraryimpl, handled via#[arbitrary(with = ...)]generators in a new gatedarbitrary_supportmodule:version_time: DateTime<FixedOffset>— bounded Unix timestamp ([0, 2100)).state: serde_json::Value— depth-bounded recursive JSON.proof: Vec<DataIntegrityProof>— emitted empty. A valid proof must be signed over the canonical log-entry bytes, which can only happen after the rest of the entry exists; andDataIntegrityProoflives inaffinidi-data-integrity. This still exercises everything up to the "is a proof present / does it authorize this update" gate plus all of SCID/hash-chain/parameter validation. This is the main design point I'd like your call on (see below).Safety / impact
#[cfg_attr(feature = "arbitrary", ...)]; no change to default builds and no new always-on dependency.examples/fuzz_validate.rs(gated on the feature) is a ready-to-copy libfuzzer target body that builds a chain and assertsvalidate()never panics.Verification
cargo build --features arbitrary✅cargo build(default, example excluded viarequired-features) ✅cargo test --features arbitrary— 435 passed, 0 failed ✅cargo clippy --features arbitrary --lib --examples— clean ✅cargo clippy(default) — clean ✅cargo run --example fuzz_validate --features arbitrary -- <random bytes>— no panic ✅Open questions for maintainers
ArbitrarytoDataIntegrityProofupstream inaffinidi-data-integrityso mutated/invalid proofs can be generated too? Empty keeps this PR self-contained; the latter widens signature-path coverage.DIDWebVHState— I left it deriving-free and assemble it from aVec<LogEntry>in the example. Happy to derive it directly if you'd prefer a one-call entry point.fuzz/cargo-fuzz workspace + a CI gate upstreamed here, or kept downstream?Marking as draft pending your steer on (1)–(3).