β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5695

Per-CPU RC4 state machine is completely unsynchronized under LWKT preemption β€” the #if 0'd crit section's premise ('no one calls in ISR/ithread') is false, so syscall-context and netisr/TCP-thread consumers interleave keystream generation, producing values outside any serialized RC4 evolution

Summary

All three entry points mutate the shared per-CPU state (i, j, sbox, numruns) with no locking - karc4random_buf's guard is #if 0'd on a falsified premise while LWKT preempts between priorities, and the generator is called from concurrently preemptible syscall threads AND netisr/TCP threads (TCP ISNs via syncache, PF scrub keys, port randomization, WireGuard/curve25519 key material). A deterministic harness preempting at every legal sub-step boundary shows 49.4% of the joint output bytes diverge from ANY serialized execution - not a valid RC4 stream. Cross-consumer word duplication explicitly tested and does NOT occur (0/1000) - impact is the broken serialization/independence contract of an exported CSPRNG, not demonstrated value reuse (hence Low). crit_enter/exit fix (the region cannot sleep).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5695 Β· 10 files
FileTypeDescriptionSize
README.md β€” 4.0 KB ↓ raw
VERDICT.md β€” 3.8 KB ↓ raw
arc4_harness.c β€” 17.3 KB view raw
build.sh β€” 102 B view raw
run.sh β€” 105 B view raw
build.log β€” 149 B view raw
run.log β€” 426 B view raw
fix.diff β€” 1.2 KB view raw
manifest.json β€” 820 B view raw
verdict.json β€” 3.7 KB view raw

DF-5695 β€” per-CPU RC4 state machine is unsynchronized under LWKT preemption

File: sys/libkern/arc4random.c (pass-2 audit) Class: CWE-362 race on a security primitive / RNG correctness (CWE-338) Verdict: reproduced (invariant-break demonstrated on exact source; reachability of concurrent same-CPU callers established in the deployed kernel's call graph)

Root cause

karc4random() / karc4random64() / karc4random_buf() (sys/libkern/arc4random.c:101-162) mutate the per-CPU RC4 state arc4_data_pcpu[mycpuid] (i, j, sbox, numruns) with no serialization. karc4random_buf() even carries the guard commented out:

#if 0
    /* No one call this function in ISR/ithread. */
    crit_enter();
#endif

(arc4random.c:147-150) β€” and karc4random/karc4random64 have no guard at all. The premise "no one calls this in ISR/ithread" is false for thread preemption: the generator is called from

  • user/syscall thread context β€” kern_exec.c:1173 (execve stack-gap ASLR), vm_map.c:4408 (mmap ASLR), pf.c:6131 (outbound pf), in_pcb.c:885 (ephemeral port randomization, outbound connect),
  • netisr / TCP protocol thread context β€” inbound pf.c:4459/4287 (scrub seqdiff / state seqhi) reached via ip_input.c:393 netisr, tcp_syncache.c:1070 (ISN), in_pcb.c:414,
  • timer-thread context β€” altq_red.c:408, plus key-material callers wg_cookie.c:416, wg_noise.c:304, curve25519.h:42, cryptoapi.c:1084.

DragonFly LWKT preempts a running thread when a higher-priority thread becomes runnable (sys/kern/lwkt_thread.c:941 lwkt_preempt(), reached via lwkt_schedule()'s td_preemptable callback). A netisr/TCP thread woken by an IPI can therefore preempt a syscall thread between any two instructions of arc4_randbyte() (arc4random.c:87-99), which performs 5 separate memory-visible state mutations/reads per byte.

Demonstrated artifact (deterministic interleave harness)

arc4_harness.c race runs the exact arc4_randbyte state machine decomposed into its five memory-visible sub-steps; a victim context is preempted at every legal sub-step boundary (20 points inside one karc4random() call) while a preempting context executes complete karc4random() calls on the same state β€” precisely what LWKT preemption permits. Results over 1000 trials:

timed_ordered_bytes=7986 divergent_from_serialized=3946 (49.4%)
cross_consumer_duplicate_words=0
  • 49.4% of the emitted bytes cannot be produced by ANY serialized execution of the same state machine β€” the joint output is not a valid RC4 stream (chimera values assembled from pre- and post-preemption state). A correct synchronized implementation is always equivalent to some serialization; this one is not.
  • Cross-consumer word duplication does not occur (0 hits) β€” the practical impact is the independence/contract violation (outputs outside the algorithm's distribution) rather than observable value reuse.

Impact assessment (honest)

No memory-safety effect (all indices are uint8_t β†’ in-bounds). No demonstrated secret-leak or prediction: chimera values remain unpredictable functions of secret state. The defect is a concurrency correctness violation of an exported CSPRNG primitive consumed for ISNs, PF scrub keys, port randomization, wg/curve25519 key material β€” statistical analysis of interleaved streams is no longer sound, and the #if 0 premise that justified removing serialization is provably false. Severity Low. FreeBSD's in-kernel arc4random carries a mutex for exactly this class of race.

Reproduce

cc -O2 -o arc4_harness arc4_harness.c -lm
./arc4_harness race 1000

Fix

fix.diff: LWKT critical sections around all three entry points (un-#if-0 the existing pair in karc4random_buf, add the pair to karc4random/karc4random64). crit_enter() blocks both interrupts and LWKT preemption on the local CPU; the region is bounded (~8 bytes generation + occasional reseed via read_random, which never sleeps on the CSPRNG_UNLIMITED path, subr_csprng.c:146), so latency impact is negligible.

VERDICT.md
↓ download raw

DF-5695 β€” VERDICT

Status: reproduced (algorithm-level invariant break on the exact source, deterministic interleave harness; deployed-kernel reachability established by call-graph analysis). Impact: none attacker-observable demonstrated (RNG contract violation: outputs outside any serialized RC4 evolution under preemption). Confidence: likely.

What was demonstrated

./arc4_harness race 1000 (exact verbatim arc4_randbyte state machine, victim preempted at all 20 sub-step boundaries of one karc4random() while a second context runs complete karc4random() calls on the same state):

  • 49.4% of the time-ordered emitted bytes diverge from the serialized reference stream started from the identical state β€” the joint output cannot be produced by any serialization of the two consumers. In a correctly serialized implementation (mutex/crit), interleaving only ever re-partitions a single valid keystream between consumers; here it does not.
  • 0 cross-consumer duplicate words in 1000 trials β€” I explicitly tested for the classic "two consumers receive the same 4-byte word" artifact and it does not arise in this construction (the preempted byte is always computed from a strictly-later state than the preemptor's). The defect's observable is chimera values + lost arc4_numruns accounting, not value reuse.

Reachability in the deployed kernel (why the #if 0 premise is false)

  • The comment "No one call this function in ISR/ithread" (arc4random.c:148) addresses interrupt context only; the race needs only thread preemption, which LWKT performs: sys/kern/lwkt_thread.c:913-941 (lwkt_preempt() via lwkt_schedule()'s td_preemptable callback).
  • Same-CPU consumer pairs that can interleave:
  • syscall thread kern_exec.c:1173 / vm_map.c:4408 / pf.c:6131 / in_pcb.c:885 ↔ netisr/TCP thread pf.c:4459 (inbound scrub, via ip_input netisr ip_input.c:393), tcp_syncache.c:1070, in_pcb.c:414, altq_red.c:408.
  • Window: a few instructions per byte, up to len bytes in karc4random_buf (callers pass 16-32B); preemption must land inside and the preemptor must call the generator β€” low per-event probability, but a busy server executes these paths continuously (SYN floods literally pump pf.c:4459).

Why not higher severity

  • All state indices are uint8_t β†’ memory-safe under any interleave.
  • Chimera outputs are still unpredictable functions of secret state; no bias, duplication, or prediction was demonstrated (duplication explicitly ruled out empirically).
  • Worst additional effect: interleaved re-entry of arc4_randomstir() (numruns race) can interleave two KSAs β€” still entropy-preserving.

Filed Low: correctness of a security primitive + falsified safety premise, no demonstrated exploitation.

Fix validation

fix.diff adds crit_enter()/crit_exit() around the state-machine use in all three entry points (re-enabling the pair the authors #if 0'd in karc4random_buf at arc4random.c:147-161, adding it to karc4random/karc4random64 which never had it). A critical section makes every execution equivalent to a serialization, restoring the invariant demonstrated broken above. Diff applies clean (git apply --check). Kernel rebuild not performed (Low severity, non-memory-corruption); semantics of crit_enter/crit_exit (blocks LWKT preemption and interrupts on the CPU) are the documented DragonFly mechanism, and the guarded region cannot sleep (read_random on the CSPRNG_UNLIMITED path takes only a spinlock, subr_csprng.c:146,127-168).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff restores serialization via LWKT critical sections in all three entry points; under crit_enter the execution is by construction equivalent to a serialized one, eliminating the demonstrated invariant break. Applies clean via git apply --check. Kernel rebuild not performed (Low severity, non-memory-corruption); guarded region cannot sleep (read_random CSPRNG_UNLIMITED path is spinlock-only, subr_csprng.c:146).

fix.diff (unified, git-apply-able); harness race-mode output in run.log demonstrating the baseline invariant break the fix removes.
↓ fix.diffper-fix-DF-5695

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5695/run.log: 'divergent_from_serialized=3946 (49.4%)' and 'cross_consumer_duplicate_words=0' over 1000 trials (plus 200/500-trial runs). README.md/VERDICT.md carry the reachability analysis with path:line citations.

PoC changes

No seed pack; the race mode was authored into the same exact-source harness as DF-5694: arc4_randbyte decomposed into its 5 memory-visible sub-steps, victim preempted at all 20 legal boundaries of one karc4random(), preemptor runs complete calls, outputs compared against the serialized reference and scanned for cross-consumer duplication.

Verified recommended fix

Wrap the state-machine use in all three entry points in crit_enter()/crit_exit() (un-#if-0 the existing pair at arc4random.c:147-161, add the pair to karc4random/karc4random64); see fix.diff.

Verdict

karc4random/karc4random64/karc4random_buf (sys/libkern/arc4random.c:101-162) mutate the per-CPU RC4 state with no serialization; the #if 0'd crit_enter at arc4random.c:147-150 rests on the premise 'no one calls in ISR/ithread', which is false for LWKT thread preemption (lwkt_thread.c:941): syscall-context callers (kern_exec.c:1173, vm_map.c:4408, pf.c:6131, in_pcb.c:885) coexist on the same CPU with netisr/TCP-thread callers (pf.c:4459 via ip_input.c:393, tcp_syncache.c:1070, in_pcb.c:414). A deterministic sub-step interleave harness running the exact arc4_randbyte state machine shows 49.4% of the time-ordered output bytes cannot be produced by ANY serialized execution (chimera values from mixed pre/post-preemption state); cross-consumer word duplication was explicitly tested and does not occur (0/1000 trials). Memory-safe (uint8_t indices), no demonstrated prediction or leak - the defect is the broken serialization invariant of an exported CSPRNG feeding ISNs, PF scrub keys, wg/curve25519 key material. Severity Low.