Skip to content

test(net): fuzz the wire codecs - #3198

Merged
kixelated merged 2 commits into
mainfrom
claude/moq-net-fuzzing-5ae8da
Aug 31, 2026
Merged

test(net): fuzz the wire codecs#3198
kixelated merged 2 commits into
mainfrom
claude/moq-net-fuzzing-5ae8da

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

  • Coverage-guided fuzzing (libFuzzer via cargo-fuzz) of the bytes a relay parses from an untrusted peer: 21 moq-lite types across 6 versions, 37 IETF types across 6 drafts, both varint codecs, and the Path invariants. just rs fuzz <target> runs it; targets are lite, ietf, varint, path.
  • The target bodies live in moq-net's hidden fuzz module (src/fuzz.rs), not in fuzz/fuzz_targets/. lite and ietf are private modules, so an outside crate cannot reach a single decoder; and sharing the bodies lets the crate's own tests replay the corpus, plus every crash input under fuzz/regressions/, on the pinned stable toolchain as part of just test. A crash found by fuzzing becomes a regression test CI runs without anyone installing cargo-fuzz. The fuzzer itself stays out of check/test because libFuzzer needs -Zsanitizer, the same way loom does.
  • Beyond "does not panic", each target asserts that what we encode we decode back consuming every byte, and that encoding is byte-stable. The seed corpus is generated from the dispatch rather than committed, and seeds_reach_every_arm fails when no seed decodes for an arm, so a target cannot silently cover less than it lists.

The first run found a real bug, fixed in the first commit. Decoding an IETF SUBSCRIBE (and PUBLISH_OK) keeps the filter type but drops the Location an absolute filter carries after it, so the struct cannot express what encoding it would need to write. Draft-14 encode guarded that with a debug_assert!, which is reachable from a peer's own message: it aborts under debug assertions, and in release it emits a truncated message whose next field the peer reads as the missing Location. It now returns EncodeError::Unsupported. The crash input is committed at fuzz/regressions/ietf/subscribe-absolute-filter, and fails the suite without the fix.

Two things the targets deliberately do not assert, both documented in the code:

  • Parameter maps encode straight out of a HashMap, so byte-stability is unasserted for moq-lite SETUP and drafts 14/15 (16+ sort by key first). The nondeterministic wire output is real but out of scope here.
  • The leading-ones varint decodes the full u64, above VarInt::MAX's documented 62-bit ceiling. The existing tests already call this out.

No CI wiring: fuzzing is a local recipe, and what gates PRs is the stable replay in just test.

Public API changes

  • moq_net::EncodeError::Unsupported: new variant. Additive, and the enum is #[non_exhaustive], so this targets main.
  • moq-net gains an off-by-default fuzz feature enabling a #[doc(hidden)] pub mod fuzz. Nothing but the harness depends on it, and it is absent from a default build.
  • No wire format change, so no drafts/ update: the fix refuses to emit a message that was never encodable correctly in the first place. Nothing to mirror in js/net either, since the change is a Rust-side error return.

Test plan

  • cargo clippy --locked --workspace --all-targets -- -D warnings, plus -p moq-net --features fuzz (which check never compiles, since it builds default features only).
  • RUSTDOCFLAGS="-D warnings" cargo doc with and without the fuzz feature. The feature build is what just rs features compiles nightly, and it caught a private intra-doc link.
  • cargo nextest run --locked --workspace --all-targets: 3266 passed.
  • cargo fmt --all --check, cargo sort --workspace --check, cargo shear, taplo format --check, bun remark --frail, just --fmt --check.
  • All four targets run through just rs fuzz: lite 5.4M execs, ietf 8.7M, varint 6.4M, path 2.6M, all clean after the fix.
  • Verified fuzz::tests::regressions fails with the debug_assert! restored and passes with the fix.

(Written by Claude Opus 5)

kixelated and others added 2 commits August 30, 2026 14:19
Decoding an IETF SUBSCRIBE (or PUBLISH_OK) keeps the filter type but drops the
Location an absolute filter carries after it, so the struct cannot express what
encoding it would need to write. Draft-14 encode guarded that with a
`debug_assert!`, which is reachable from a peer's own message: it aborts under
debug assertions, and in release it emits a truncated message whose next field
the peer reads as the missing Location.

Return the new `EncodeError::Unsupported` instead. Found by the fuzz target
added in the following commit; the crash input is committed there as a
regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Coverage-guided fuzzing (libFuzzer via cargo-fuzz) of the bytes a relay parses
from an untrusted peer: 21 moq-lite types across 6 versions, 37 IETF types
across 6 drafts, both varint codecs, and the `Path` invariants.
`just rs fuzz <target>` runs it.

The target bodies live in moq-net's hidden `fuzz` module rather than in
`fuzz/fuzz_targets/`, for two reasons. `lite` and `ietf` are private modules, so
an outside crate cannot reach a single decoder. And sharing the bodies lets the
crate's own tests replay the generated corpus, plus every crash input committed
under `fuzz/regressions/`, on the pinned stable toolchain as part of `just test`
-- so a crash found by fuzzing becomes a regression test CI runs without anyone
installing cargo-fuzz. libFuzzer needs `-Zsanitizer`, so the fuzzer itself stays
out of `check` and `test` the way `loom` does.

Beyond "does not panic", each target asserts that what we encode we decode back
consuming every byte, and that encoding is byte-stable. The seed corpus is
generated from the dispatch rather than committed, and `seeds_reach_every_arm`
fails when nothing decodes for an arm, so a target cannot silently cover less
than it lists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T21:24:02.812094Z 9fd5ec2 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 14 minutes.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f3f4e301-fa40-4784-8bbc-daf470116da5

📥 Commits

Reviewing files that changed from the base of the PR and between 177c205 and 9fd5ec2.

⛔ Files ignored due to path filters (1)
  • rs/moq-net/fuzz/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • rs/CLAUDE.md
  • rs/justfile
  • rs/moq-net/Cargo.toml
  • rs/moq-net/examples/fuzz-seeds.rs
  • rs/moq-net/fuzz/.gitignore
  • rs/moq-net/fuzz/Cargo.toml
  • rs/moq-net/fuzz/README.md
  • rs/moq-net/fuzz/fuzz_targets/ietf.rs
  • rs/moq-net/fuzz/fuzz_targets/lite.rs
  • rs/moq-net/fuzz/fuzz_targets/path.rs
  • rs/moq-net/fuzz/fuzz_targets/varint.rs
  • rs/moq-net/fuzz/regressions/README.md
  • rs/moq-net/fuzz/regressions/ietf/subscribe-absolute-filter
  • rs/moq-net/src/coding/encode.rs
  • rs/moq-net/src/fuzz.rs
  • rs/moq-net/src/ietf/publish.rs
  • rs/moq-net/src/ietf/subscribe.rs
  • rs/moq-net/src/lib.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kixelated
kixelated merged commit 37240a3 into main Aug 31, 2026
4 checks passed
@kixelated
kixelated deleted the claude/moq-net-fuzzing-5ae8da branch August 31, 2026 03:06
This was referenced Aug 31, 2026
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