SYN-cookie crypto state global unsynchronized across netisr CPUs: racy MD5_CTX + tcp_secret[] defeats SYN-flood mitigation
Summary
static MD5_CTX syn_ctx(:1358) + tcp_secret[SYNCOOKIE_NSECRETS](:1351-1354) are single global no lock no per-CPU. syncookie_generate called from syncache_add(:1068) on every new entry when tcp_syncookies enabled(default). syncookie_lookup from syncache_expand(:914). TCP input runs per-CPU netisr, SYNs distributed across CPUs by mbuf hash. 2+ CPUs simultaneously MD5Init/Update/Final against SAME syn_ctx + read/write tcp_secret[idx] -> interleaving corrupts transform+secret. Generated cookie sc_iss(:1406-1425) and recomputed digest(:1442-1463) become garbage -> legitimate final ACKs rejected under flood when syncache overflows. Defeats SYN-cookie mitigation exactly when engaged. Fix: per-CPU tcp_secret+MD5_CTX or spinlock around crypto.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0484 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trace.md | source-trace | line-by-line trace of the unsynchronized syn_ctx/tcp_secret race | 3.9 KB | β raw |
| fix.diff | suggested-fix | spinlock around syncookie crypto+secret in generate/lookup | 1.7 KB | view raw |
| VERDICT.md | verdict | full narrative | 2.0 KB | β raw |
| README.md | readme | summary | 3.2 KB | β raw |
| env.txt | environment | guest uname, modules, HW-gate note | 188 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0484 β SYN-cookie global crypto state unsynchronized across netisr CPUs
Verdict: NOT REPRODUCED at runtime β race is real by inspection; impact = defeats SYN-cookie mitigation (logic/DoS)
The bug (real, by source inspection)
sys/netinet/tcp_syncache.c:
- static MD5_CTX syn_ctx; (:1358) β a single global MD5 context.
- static struct { u_int32_t ts_secbits[4]; u_int ts_expire; }
tcp_secret[SYNCOOKIE_NSECRETS]; (:1351-1354) β single global secret
table. No per-CPU copy, no lock.
- syncookie_generate() (:1382-1426) does MD5Init(&syn_ctx) β¦ MD5Update β¦
MD5Final, and reads/writes tcp_secret[idx].ts_secbits[] / .ts_expire.
- syncookie_lookup() (:1428-1463) does the same MD5Init/Update/Final on the
same syn_ctx and reads tcp_secret[idx].
TCP input runs per-CPU in netisr; inbound SYNs are distributed across CPUs by
mbuf hash. With β₯2 CPUs, syncookie_generate (from syncache_add, :1068, on
syncache overflow) and syncookie_lookup (from syncache_expand, :914) can
run concurrently on different CPUs against the same syn_ctx and
tcp_secret[] slot. MD5 is a streaming transform: interleaved
Init/Update/Final on one context corrupts the digest, and a torn
ts_secbits read/write makes the generated cookie sc_iss (:1406-1425) and
the recomputed digest (:1442-1463) disagree β legitimate final ACKs are
rejected β the SYN-cookie flood mitigation fails exactly when it engages.
This is CWE-362 (race). It is a logic / DoS defect (no memory corruption, no info leak, no write primitive) β there is no escalation chain to develop.
Why not reproduced at runtime
Triggering requires: β₯2 CPUs receiving SYNs concurrently, syncache overflow forcing syncookie mode, and the rare MD5 interleave that actually corrupts a cookie a live connection then depends on. It is a low-probability race whose effect (one dropped legitimate ACK) is indistinguishable from normal packet loss on a SYN-flooded link. No deterministic PoC is feasible in the audit window; the defect is established by the unsynchronized single-global-state design, which is self-evidently racy on an SMP netisr.
Privilege boundary
Network-reachable, unauthenticated (AV:N, PR:N): any peer that can send SYNs to a listening socket contributes to the race. No local privilege needed.
Fix (validated as applies + compiles + boots)
fix.diff serialises the crypto+secret access with a spinlock:
static struct spinlock syncookie_sl = SPINLOCK_INITIALIZER(0, 0);
spin_lock(&syncookie_sl) β¦ (secret refresh + MD5Init/Update/Final) β¦
spin_unlock(&syncookie_sl) in both syncookie_generate and syncookie_lookup
(releasing before the early return NULL in lookup).
Built into the combined single-fix kernel (#1, kern.version #1); boots clean.
(A per-CPU tcp_secret+MD5_CTX would remove the contention but is a larger
change; the spinlock is the minimal correct fix.) fix_status = not_testable
(the race has no deterministic runtime marker to compare).
Files
trace.mdβ line-by-line source trace of the racefix.diffβ spinlock around the syncookie crypto+secretREADME.md,VERDICT.md,manifest.json
DF-0484 detailed verdict
Verdict: NOT REPRODUCED at runtime β real race by inspection (CWE-362), logic/DoS impact
Mechanism
tcp_syncache.c uses a single global MD5_CTX syn_ctx (:1358) and a
single global tcp_secret[SYNCOOKIE_NSECRETS] (:1351-1354) with no lock
and no per-CPU copy. syncookie_generate (:1382, called from syncache_add
:1068) and syncookie_lookup (:1428, from syncache_expand :914) both run
MD5Init/Update/Final on that shared context and read/write tcp_secret[].
TCP input is per-CPU netisr, so on β₯2 CPUs these execute concurrently against
the same state, interleaving the streaming MD5 transform and tearing
ts_secbits reads/writes. Result: generated cookie (sc_iss, :1406-1425) and
recomputed digest (:1442-1463) disagree β legitimate completing ACKs rejected
under SYN flood β SYN-cookie mitigation defeated exactly when engaged.
Impact ceiling
Logic/DoS only. No corruption, no leak, no write primitive β no escalation chain (this is not a memory-corruption finding; Phase 6 does not apply).
Why not reproduced
Low-probability SMP race with no deterministic trigger; observable effect (one
dropped ACK) is indistinguishable from normal loss on a flooded link.
Established by the lockless single-global-state design (see trace.md).
Privilege boundary
Network, unauthenticated (AV:N/PR:N). No local privilege required to contribute SYNs to the race.
Fix (applies + compiles + boots)
fix.diff adds static struct spinlock syncookie_sl and wraps the
secret-refresh + MD5Init..Final region in spin_lock/spin_unlock in both
functions (releasing before the early return NULL in syncookie_lookup).
Built in the combined single-fix kernel (#1); boots clean. A per-CPU
secret+context would remove contention but is a larger change. fix_status =
not_testable (no deterministic runtime marker).
PoC changes
No seeded PoC. I authored trace.md (line-by-line race trace) and fix.diff.
A deterministic PoC is infeasible for this race class.
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)
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.)
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. SYN-cookie global MD5_CTX+tcp_secret unsynchronized across CPUs -> torn crypto. Race not deterministically reproducible.
No comments yet.