Skip to content

Add sanitizer and fuzzing support with CI coverage#144

Merged
gafferongames merged 2 commits into
mainfrom
fuzzing-and-sanitizers
Jul 9, 2026
Merged

Add sanitizer and fuzzing support with CI coverage#144
gafferongames merged 2 commits into
mainfrom
fuzzing-and-sanitizers

Conversation

@gafferongames

Copy link
Copy Markdown
Contributor

Summary

Hardens netcode's hostile-input surface with sanitizers and fuzzing, wired into CI. Both CMake options are off by default, so the existing 6-cell build matrix is unchanged.

CMake options

  • NETCODE_SANITIZE — builds everything with AddressSanitizer + UndefinedBehaviorSanitizer. The vendored sodium subset is exempted from UBSan (third-party SIMD crypto uses intentional type punning / unaligned access that UBSan flags but isn't netcode's to fix) but still gets ASan.
  • NETCODE_FUZZ — builds the fuzz/ harnesses. Where the compiler ships libFuzzer (LLVM clang) they are libFuzzer targets; elsewhere (AppleClang, GCC, MSVC) they build as a standalone file replayer, so the harnesses compile everywhere and a crashing input reproduces anywhere.

Fuzz harnesses (fuzz/)

Cover the code paths that parse untrusted bytes, each with a write/read round-trip invariant:

  • fuzz_read_packetnetcode_read_packet, the reader every client/server runs on received datagrams; both raw-input and encrypted round-trip paths, including the connect-token request path (token encrypted with the real private key so the full decrypt+read is exercised).
  • fuzz_connect_token — the public and private connect token readers, plus a private-token round-trip.
  • fuzz_parse_addressnetcode_parse_address plus a to_stringparse round-trip.

CI

Two new jobs on ubuntu-24.04 with clang:

  • sanitizers — configure -DNETCODE_SANITIZE=ON, build all, run ctest with halt_on_error.
  • fuzz (smoke) — build the fuzz targets, run each for 60s bounded, upload any crash-*/oom-*/timeout-* inputs as an artifact on failure.

Test plan

  • Default build (options off) unchanged: builds + 37 tests pass locally
  • NETCODE_SANITIZE=ON: builds clean, all tests pass under ASan+UBSan locally (Apple Silicon)
  • NETCODE_FUZZ=ON: all three harnesses build (standalone fallback on AppleClang) and run clean over ~200 random inputs under ASan/UBSan
  • CI: real coverage-guided libFuzzer run on the Linux clang leg (first run — this is where actual fuzzing happens, since no local LLVM clang)

🤖 Generated with Claude Code

gafferongames and others added 2 commits July 9, 2026 00:59
Adds two CMake options, both off by default so the existing build and CI
matrix are unaffected:

- NETCODE_SANITIZE: builds everything with AddressSanitizer +
  UndefinedBehaviorSanitizer. The vendored sodium subset is exempted from
  UBSan (third-party SIMD crypto uses intentional type punning / unaligned
  access) but still gets ASan.
- NETCODE_FUZZ: builds the fuzz/ harnesses. Where the compiler ships
  libFuzzer (LLVM clang) they are libFuzzer targets; elsewhere they build as
  a standalone file replayer so they compile everywhere and crashes reproduce
  anywhere.

Fuzz harnesses cover netcode's untrusted-input surface, each with write/read
round-trip invariants:

- fuzz_read_packet: netcode_read_packet (the socket packet reader), both raw
  input and encrypted round-trip paths including the connect token request.
- fuzz_connect_token: the public and private connect token readers.
- fuzz_parse_address: netcode_parse_address plus a to_string round-trip.

CI gains a Linux ASan+UBSan leg (builds all, runs ctest) and a bounded
smoke-fuzz leg (60s per target, uploads any crash inputs as an artifact).

BUILDING.md and fuzz/README.md document usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
netcode_replay_protection_already_received computed
"sequence + NETCODE_REPLAY_PROTECTION_BUFFER_SIZE <= most_recent_sequence",
which wraps for sequence values in the top 256 of the uint64 space and
falsely rejects legitimate packets as replays. Not reachable in practice
(sequences start at 0 / 1<<63 and increment per packet), but wrong, and in
security-relevant code.

Found by fuzz_read_packet's round-trip invariant within the first minute of
the first CI smoke-fuzz run, on input sequence 0xFFFFFFFFFFFFFF00.

The check is now written subtraction-side so it cannot overflow, and
test_replay_protection covers the top of the sequence space, including that
replays up there are still caught.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gafferongames gafferongames merged commit 102dc48 into main Jul 9, 2026
8 checks passed
@gafferongames gafferongames deleted the fuzzing-and-sanitizers branch July 9, 2026 05:16
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