Repository navigation
test: add native fuzz targets for untrusted decoder boundaries - #29
Merged
Merged
Conversation
Companion to the PR #28 hardening merge: durable native Go fuzz coverage for the three untrusted-input decoding boundaries this repo exposes, with semantic invariants on successful decodes and concrete rejection categories on malformed input. No production code, dependency, or doc changes. - crypto: FuzzECPointGobDecode drives Gob-decoding an ECPoint. Valid/canonical seeds and any successful decode assert curve identity (SameCurve vs tss.EC()), on-curve hold, coordinate equality against the canonical seed, and gob round-trip stability. Malformed shapes (truncated, off-curve) assert a deterministic error, not a panic. - crypto/paillier: FuzzUnmarshalModProof drives UnmarshalModProof through a u32/u16 framing parser that mirrors the wire section layout. A successful decode asserts exact PARAM_M part arity and per-element value equality against the decoded seed; short/long arities assert a specific rejection. Section counts are capped (capFramedParts) before allocation. - crypto/mta: FuzzRangeProofAliceFromBytes drives RangeProofAliceFromBytes through its u16 framing parser. A successful decode asserts exact six-arity, value equality for the S/S1/S2 fields, ValidateBasic, and ValidateProof; non-six arities assert rejection. Counts capped before allocation. - crypto/dlnproof: FuzzUnmarshalDLNProof drives UnmarshalDLNProof through a u16 framing parser. A successful decode asserts exact Iterations arity for both Alpha and T and per-element value equality; off-arity asserts rejection. Counts capped before allocation. - tss: FuzzParseWireMessage drives ParseWireMessage on wrapped content bytes. Successful parses assert routing round-trip (From identity, IsBroadcast flag), TypeUrl registration, and proto round-trip stability; unknown type URLs and truncated inputs assert an error, not a panic. Recommended fuzz commands (run from the repo root): go test ./crypto -run '^$' -fuzz=FuzzECPointGobDecode -fuzztime=1m go test ./crypto/paillier -run '^$' -fuzz=FuzzUnmarshalModProof -fuzztime=1m go test ./crypto/mta -run '^$' -fuzz=FuzzRangeProofAliceFromBytes -fuzztime=1m go test ./crypto/dlnproof -run '^$' -fuzz=FuzzUnmarshalDLNProof -fuzztime=1m go test ./tss -run '^$' -fuzz=FuzzParseWireMessage -fuzztime=1m Plain `go test ./...` runs the deterministic seed tests without fuzzing; the -fuzz flag activates the targets above.
Address review findings on the fuzz targets: - tss/wire: use proto.Marshal for the unknown-type seed (not the method value); assert a fresh-content round-trip — a fresh Any is built from the decoded content via anypb.New and re-marshaled, so the invariant exercises the decoded -> re-encoded boundary instead of reusing the original Any bytes; - paillier/mta/dlnproof: replace per-field SetBytes copy-restatement assertions with the decoder's contract classification — exact-arity non-empty shapes must decode to a non-nil/basic-valid proof, wrong arity or empty parts must be rejected; - drop the companion Test...Seeds/semantics tests: Go executes fuzz seed corpora during ordinary `go test`, so they duplicated the seeds; - remove now-dead helpers and imports.
The decoder fuzz targets built a second binary framing parser (u16/u32 section layout, readers/writers, seed structs) around the production decoders. SetBytes accepts arbitrary byte syntax, so that framing only added an obscured second parser. Derive the decoder arguments directly from native fuzz primitives instead: - paillier: (nx, na, nb, nz uint16, fill, emptyWS byte) -> exact PARAM_M arity + non-empty W must decode to a populated proof; otherwise the decoder must reject. - mta: (n uint16, fill, emptyAt byte) -> six non-empty parts must decode and ValidateBasic; a wrong count or emptied part must reject. - dlnproof: (na, nt uint16, fill byte) -> exact Iterations arity on both sides must decode; otherwise reject. Count caps (capFramedParts=1024) retained before allocation; the valid/short/long/empty seed shapes are kept. ECPoint and wire targets are unchanged.
Author
|
Integration verification completed before merge into
|
5 tasks
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.
Why
After PR #28 merged the proof input decoding and modulus bounds hardening into dev, this test-only PR adds durable native Go fuzz coverage for the three untrusted-input decoding boundaries the repo exposes. The goal is long-lived regression protection: any future refactor that subtly changes a decoder's contract or reopens a malformed-input hole will surface under the fuzz runner.
No production code, dependency, or doc changes.
Design
Every decoder argument is derived directly from native fuzz primitives (
uint16counts,bytefill values, flags). The production decoders care only about arity and empty/non-empty shape —big.Int.SetBytesaccepts arbitrary byte syntax — so no custom framing parser, seed structs, or reader/writer scaffolding is introduced. Counts are capped (capFramedParts = 1024) before slice allocation so a tiny fuzz value cannot amplify into a large allocation. Each target classifies every generated shape against the decoder's contract and pins the rejection category, not wording. No key generation or expensive crypto on the hot path; all targets are deterministic and safe under parallel fuzz workers.Targets and invariants
crypto—FuzzECPointGobDecode(crypto/ecpoint_fuzz_test.go)GobDecodeasserts:ValidateBasic(on-curve, non-identity),SameCurve(pt.Curve(), tss.EC())(curve identity), and re-encode + re-decode reproduces anEquals-equal point on the same curve (catches swapped, mis-ordered, or sign-flipped coordinate decoding).crypto/paillier—FuzzUnmarshalModProof(crypto/paillier/mod_proof_fuzz_test.go)(nx, na, nb, nz uint16, fill, emptyWS byte).PARAM_Marity on X/A/B/Z and non-empty W ⇒ must decode to a non-nil proof withWpopulated.crypto/mta—FuzzRangeProofAliceFromBytes(crypto/mta/range_proof_fuzz_test.go)(n uint16, fill, emptyAt byte)—emptyAtnon-zero empties one interior part.RangeProofAliceBytesParts(6) non-empty parts ⇒ must decode and passValidateBasic.crypto/dlnproof—FuzzUnmarshalDLNProof(crypto/dlnproof/proof_fuzz_test.go)(na, nt uint16, fill byte).Iterationsalphas andIterationsts parts ⇒ must decode to a non-nil proof.tss—FuzzParseWireMessage(tss/wire_fuzz_test.go)ParseWireMessageon wrapped content bytes.Anyis built from the decoded content viaanypb.Newand re-marshaled, re-parsed, and the content must beproto.Equalto the first parse (exercises the decoded → re-encoded boundary, not a reuse of the original bytes); senderFromidentity andIsBroadcastflag round-trip.Recommended fuzz commands (repo root)
Plain
go test ./...runs the fuzz seed corpora without the fuzz runner; add-fuzzto activate a target. Quick corpus check:-fuzztime=5s.Targeted head / files
test/input-decoder-fuzzdevcrypto/ecpoint_fuzz_test.gocrypto/paillier/mod_proof_fuzz_test.gocrypto/mta/range_proof_fuzz_test.gocrypto/dlnproof/proof_fuzz_test.gotss/wire_fuzz_test.go