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)
PoC verification
Evidence pack
findings/poc/DF-5695 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 viaip_input.c:393netisr,tcp_syncache.c:1070(ISN),in_pcb.c:414, - timer-thread context β
altq_red.c:408, plus key-material callerswg_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.
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_numrunsaccounting, 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()vialwkt_schedule()'std_preemptablecallback). - 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 threadpf.c:4459(inbound scrub, via ip_input netisrip_input.c:393),tcp_syncache.c:1070,in_pcb.c:414,altq_red.c:408. - Window: a few instructions per byte, up to
lenbytes inkarc4random_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 pumppf.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
fixedfix.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.
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.
No comments yet.