# DF-0484 source trace — unsynchronized syncookie crypto state

Claim: the SYN-cookie transform uses process-global, lockless state that two
netisr CPUs can corrupt concurrently.

## The shared state (no per-CPU copy, no lock)
`sys/netinet/tcp_syncache.c`:
```
1351: static struct {
1352:     u_int32_t ts_secbits[4];
1353:     u_int ts_expire;
1354: } tcp_secret[SYNCOOKIE_NSECRETS];          <-- ONE global secret table
...
1358: static MD5_CTX syn_ctx;                      <-- ONE global MD5 context
1360: #define MD5Add(v) MD5Update(&syn_ctx, (u_char *)&v, sizeof(v))
```

## Producer: syncookie_generate (called on syncache overflow)
```
1382: syncookie_generate(struct syncache *sc)
1395:     idx = ((ticks << SYNCOOKIE_TIMESHIFT) / hz) & SYNCOOKIE_WNDMASK;
1396:     if (tcp_secret[idx].ts_expire < ticks) {      <-- READ/WRITE shared secret
1397:         for (i = 0; i < 4; i++)
1398:             tcp_secret[idx].ts_secbits[i] = karc4random();
1399:         tcp_secret[idx].ts_expire = ticks + SYNCOOKIE_TIMEOUT;
1400:     }
1406:     MD5Init(&syn_ctx);                             <-- uses GLOBAL syn_ctx
...       MD5Add(...) x several
1423:     MD5Final((u_char *)&md5_buffer, &syn_ctx);
1424:     data ^= (md5_buffer[0] & ~SYNCOOKIE_WNDMASK);
```
Called from `syncache_add` (:1068) — i.e. on every new syncache entry when
`tcp_syncookies` is enabled (default). On syncache **overflow** the whole table
is dropped and syncookie mode is the only admission path.

## Consumer: syncookie_lookup (called on the final ACK)
```
1428: syncookie_lookup(struct in_conninfo *inc, struct tcphdr *th, struct socket *so)
1439:     if (tcp_secret[idx].ts_expire < ticks || ...)  <-- READ shared secret
1442:     MD5Init(&syn_ctx);                             <-- uses GLOBAL syn_ctx
...       MD5Add(...) x several
1462:     MD5Final((u_char *)&md5_buffer, &syn_ctx);
1463:     data ^= md5_buffer[0];
```
Called from `syncache_expand` (:914) to validate the cookie on the completing
ACK.

## Why it races
TCP input is per-CPU netisr (`tcp_input` runs on the CPU hashed from the mbuf).
SYNs and their final ACKs for different 4-tuples land on different CPUs. Two
CPUs executing `syncookie_generate`/`syncookie_lookup` concurrently both
`MD5Init` the *same* `syn_ctx` and then interleave `MD5Update`/`MD5Final`
calls. MD5 is a streaming hash whose internal state (A,B,C,D + bit count +
buffer) is mutated by every Update/Final; interleaving produces a digest that
depends on the interleaving, not on either connection's inputs alone. A torn
read of `tcp_secret[idx].ts_secbits[]` during the `karc4random` refresh adds a
second corruption vector. The net effect: the `data` value the producer folded
into `sc_iss` no longer equals the value the consumer recomputes ⇒ the
legitimate completing ACK is rejected (the cookie "doesn't decode") ⇒ under a
SYN flood that triggered syncookie mode, real connections can't complete.

## Synchronisation present? None.
grep for any lock around `syn_ctx`/`tcp_secret` in tcp_syncache.c: there is no
`lockmgr`/`spinlock`/`lwkt_sendmsg`-to-a-single-CPU wrapping the
generate/lookup crypto. The only serialisation is the implicit per-CPU
netisr model, which here is the *source* of the concurrency, not a guard.

## Impact classification
No memory corruption (no overflow/UAF), no info leak, no write primitive. The
defect degrades a DoS-mitigation feature. CWE-362 (race) / logic-DoS. There is
no privilege-escalation chain to develop.

## Reproduction attempt / outcome
A deterministic PoC is not feasible: the race needs concurrent SYNs on ≥2 CPUs,
syncache overflow, *and* an interleaving that actually corrupts a cookie a live
connection then depends on — and the observable (one dropped ACK) is
indistinguishable from loss on a flooded link. The defect is established by the
lockless single-global-state design above. (A statistical flood harness could
measure elevated ACK-drop rate under syncookie mode on a multi-CPU guest, but
that is a benchmark, not a deterministic repro.)
