Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / docs / superpowers / plans / 2026-06-11-p11-slice113-post-registration-gating-fuzzing.md
4.8 kB

P11 slice 113 — post-registration gating fuzzing#

Date: 2026-06-11 Closes: testing-plan §4 fuzzing gap — post_registration gating ("Golden-only behaviors lacking fuzz"). golden_post_registration.rs exists; there is no proptest.

Scope#

Fuzz the pre-/post-registration command gate — the highest-value remaining §4 gap after MODE (already done) and nick-collision (slice 112). This is the security boundary that decides which commands a connection may run before it has completed NICK+USER.

The gate lives in two layers:

  • Session::dispatch (leveva/src/session.rs): phase-independent carve-outs — QUIT (closes in either phase), CAP (phase-independent), and AWAY only when the connection negotiated the draft/pre-away cap. Then the pending_complete guard, then the phase match: Registeredcommand::dispatch, RegisteringRegistration::apply.
  • Registration::apply (leveva/src/registration.rs): the pre-registration verb match — NICK/USER/PASS advance state, QUIT is Pending, everything else → 451 ERR_NOTREGISTERED.

The pre-registration exempt set is exactly {NICK, USER, PASS, CAP, QUIT} (plus AWAY only with the pre_away cap, which the fuzzed sessions never negotiate). Every other verb — known post-reg command or junk alike — yields 451 before registration and never 451 after registration (a known verb dispatches; an unknown verb → 421).

Why a proptest (not a differential)#

The oracle also 451s unregistered clients, but the registration-error numeric skeleton is already pinned against the oracle by registration_error_skeleton_differential.rs (slice 105). The §4 gap is specifically the missing fuzz of the gate's monotonicity, so this is a model-lockstep proptest, not a new differential. The model is trivial and independent: registered = (seen_valid_nick && seen_valid_user); gated verbs 451 iff !registered.

Locked decisions#

  • Open server fixture: allow { host "*@*"; class "c" } with no password, so a valid NICK+USER registers synchronously (no auth pipeline → ctx built with None; no PASS wall; no CAP negotiation in the fuzzed streams).
  • A "gated verb" is any [A-Z]{1,12} token not in the exempt set. This is broader than a hand-picked post-reg verb pool: it also covers unknown verbs (which still 451 pre-reg and 421 post-reg — never 451), exercising the gate's generality.
  • Headline invariant (P2) is the before/after pairing — the same gated verb is 451 before registration and not-451 after. This is both the security property and its inverse.

Tests#

Unit (leveva/src/registration.rs, inverse-invariant) — 2#

  • stray_commands_never_open_registration — feeding a sequence of non-registration commands through apply only ever returns Reply([451]), never Complete, and is_registered() stays false (the gate never opens spuriously — the inverse of "NICK+USER completes").
  • pass_then_stray_still_refuses_and_does_not_registerPASS alone advances nothing observable; a following stray still 451s and the connection is still unregistered (PASS is exempt from 451 but is not a registration trigger on its own).

Proptest (leveva/tests/post_registration_proptest.rs) — 4#

  1. gated_verb_before_registration_is_451_and_does_not_register — fresh session, random gated verb + args → output contains 451, session stays unregistered, well-formed CRLF.
  2. the_gate_flips_once_same_verb_451_before_not_after — the headline + inverse: session A (unregistered) 451s the verb; session B (registered, welcome drained) feeds the same verb+args and the output does not contain 451.
  3. registration_completes_once_order_independent_strays_stay_gatedK strays, then NICK/USER in a random order separated by more strays, then J strays: every stray before both of NICK+USER land 451s and leaves the session unregistered; after both, registered; exactly one 001 across the whole stream; a trailing stray no longer 451s.
  4. arbitrary_command_bytes_never_panic_either_phase — arbitrary bytes as a command line fed to a fresh and to a registered session never panic and yield only well-formed CRLF lines.

Divergences#

None — leveva-native characterization (no leveva source change expected; the gate already behaves correctly, it was simply never fuzzed). Joins the coverage-closing characterization slices (100/102/109/110-char/111/112).

Gate#

cargo test -p leveva --lib (+2 units) green; the new post_registration_proptest binary green; golden_post_registration still green; new files clippy-clean; cargo build --workspace 0 warnings.