Skip to content

Re-seed server global packet sequence on start, not just create#159

Merged
gafferongames merged 2 commits into
mainfrom
fix-global-sequence-restart
Jul 12, 2026
Merged

Re-seed server global packet sequence on start, not just create#159
gafferongames merged 2 commits into
mainfrom
fix-global-sequence-restart

Conversation

@gafferongames

Copy link
Copy Markdown
Contributor

The bug

Global packets (connection challenge, connection denied) are encrypted with the same per-connect-token server-to-client key as per-client packets, whose sequence numbers start at zero. To keep the AEAD nonces disjoint under that shared key, the server's global sequence starts at 1ULL << 63 — but that seed was only applied in netcode_server_create, and netcode_server_stop resets the global sequence to zero.

A server object that is stopped and started again therefore sends challenge packets from global sequence 0, 1, 2 … — inside the per-client sequence space. Once a client connects, the server encrypts two different plaintexts (a challenge packet and a keep-alive/payload packet) with the same key and the same nonce, which voids the AEAD guarantees for those packets.

The fix

Seed global_sequence = 1ULL << 63 in netcode_server_start as well, so the invariant holds for every running lifetime of the server object. No wire format change — the sequence seed is server-local behavior.

  • Pinned by the new test_server_restart_global_sequence (44 tests pass).
  • Written up as finding 3 in IMPLEMENTERS.md, since ports that copied the create/stop behavior may share the bug.
  • Found while porting netcode to Rust (mas-bandwidth/netcode.rs).

🤖 Generated with Claude Code

Glenn Fiedler and others added 2 commits July 12, 2026 16:26
Global packets (connection challenge, connection denied) encrypt with
the same per-connect-token server-to-client key as per-client packets,
whose sequences start at zero. The global sequence therefore starts at
1ULL << 63 so the AEAD nonces stay disjoint under the shared key.

That seed was only applied in netcode_server_create, and
netcode_server_stop reset the global sequence to zero. A server that
was stopped and started again sent challenge packets from global
sequence 0, 1, 2 ... overlapping the per-client sequence space: the
same key and nonce encrypting two different plaintexts, which breaks
the AEAD security guarantees.

Seed the global sequence in netcode_server_start as well. No wire
format change. Pinned by test_server_restart_global_sequence.

Found while porting netcode to Rust (mas-bandwidth/netcode.rs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds finding 3: the server global packet sequence must be re-seeded to
1 << 63 on every start, not just in create, or a stopped-and-restarted
server reuses AEAD nonces between global and per-client packets under
the shared per-token server-to-client key. Fixed in dc21b70. Ports that
copied the create/stop behavior should check their restart path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gafferongames gafferongames merged commit bd1ae7c into main Jul 12, 2026
12 checks passed
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