Predictable RNG: /dev/urandom+getrandom+kern.random return deterministic ChaCha20 keystream (zero key) before first reseed
| Field | Value |
|---|---|
| ID | DF-0220 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N |
| CWE | CWE-338 Use of Cryptographically Weak PRNG |
| File | sys/kern/subr_csprng.c |
| Lines | 84-166 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-30 |
| Reported | pending |
Summary
csprng_init() zeroizes both the key and cipher context (:84-85) but never
calls chacha_keysetup(). The only readiness gate (:146) blocks non-unlimited
readers when reseed_cnt == 0, but /dev/urandom, getrandom(2), and
kern.random all pass CSPRNG_UNLIMITED, bypassing it. Before the first
successful Fortuna reseed, all three interfaces emit ChaCha20 keystream from
the all-zero (key, counter) pair β a byte-for-byte reproducible sequence
identical on every booted machine. This makes all boot-time cryptographic
material (SSH keys, TCP ISNs, IP IDs, UUIDs, MAC addresses) predictable.
Root cause
Initialization (csprng_init, :80-90):
bzero(state->key, sizeof(state->key)); // :84 β all zeros
bzero(&state->cipher_ctx, sizeof(state->cipher_ctx)); // :85 β never keyed
state->reseed_cnt = 0; // :89
Readiness gate (csprng_get_random, :146):
if ((flags & CSPRNG_UNLIMITED) == 0 && state->reseed_cnt == 0) {
ssleep(state, &state->spin, 0, "csprngrsd", 0);
goto again;
}
Only blocks when CSPRNG_UNLIMITED is NOT set.
Output (:152):
chacha_encrypt_bytes(&state->cipher_ctx, NULL, out, cnt);
Runs on the still-all-zero cipher context.
Callers passing CSPRNG_UNLIMITED:
- /dev/urandom: sys/kern/kern_memio.c:352
- getrandom(2): sys/kern/kern_nrandom.c:770
- kern.random sysctl: sys/kern/kern_nrandom.c:739
- arc4random(): keys from read_random unlimited (sys/libkern/arc4random.c:60)
Reseed trigger (csprng_reseed, :188-191):
if (state->pool[0].bytes < MIN_POOL_SIZE) {
state->failed_reseeds++;
return;
}
Pool[0] needs β₯ 96 bytes of entropy AND ratecheck() must fire. Entropy is
scattered round-robin across 32 pools (:272-274), so pool[0] commonly
receives < 96 bytes during early boot.
Threat model & preconditions
- Attacker position: Local or partially remote (via early-boot network services). Anyone who can predict the boot window.
- Impact: ALL cryptographic material derived from the kernel RNG during the pre-reseed window is 100% predictable:
- SSH/SSL session keys (via arc4random)
- TCP initial sequence numbers (
sys/netinet/tcp_subr.c:1702,1705,1741) - IP IDs (
sys/netinet/ip_id.c:122) - UUIDs (
sys/kern/kern_uuid.c:89,126) - Generated NIC MAC addresses (
sys/dev/netif/re/re.c:3521) - Required config: Default kernel. Entropy-poor systems (embedded, virtio without virtio-rng, headless) may have an indefinite window.
Proof of concept
# On a freshly booted DragonFlyBSD: dd if=/dev/urandom bs=64 count=1 | xxd # Compare against ChaCha20(key=0^32, counter=0^16, 64 bytes) # β they match exactly when captured before first reseed. # Repeating on a second identical boot yields the identical bytes.
Recommended fix
Gate unlimited output on readiness, or key the cipher at init:
--- a/sys/kern/subr_csprng.c
+++ b/sys/kern/subr_csprng.c
@@ -144,6 +144,11 @@
again:
/*
* If no reseed has occurred yet, we can't possibly give out
* any random data.
+ * Even unlimited readers should block until the first reseed,
+ * because the cipher context is all-zero until keyed.
*/
- if ((flags & CSPRNG_UNLIMITED) == 0 && state->reseed_cnt == 0) {
+ if (state->reseed_cnt == 0) {
+ if (flags & CSPRNG_UNLIMITED) {
+ /* Non-blocking: return 0 or fall back */
+ return 0;
+ }
ssleep(state, &state->spin, 0, "csprngrsd", 0);
goto again;
}
Additionally: csprng_init should key the cipher with at least hashed boot
timestamp data rather than leaving it all-zero, so any leak through the gate
is not trivially reproducible.
References
- Fortuna algorithm: Ferguson, Schneier, "Practical Cryptography"
- Linux
getrandom(2): blocks untilCRNG_READY - FreeBSD
random(4):/dev/urandomblocks until seeded
Timeline
- 2026-06-30 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0220 Β· 27 files| File | Type | Description | Size | |
|---|---|---|---|---|
| rand_probe.c | trigger-source | unprivileged 4-source RNG probe (unchanged from prior pass) | 2.6 KB | view raw |
| zero_scan.c | trigger-source | unprivileged early-boot window scanner: 3 interfaces, all-zero-block detector | 2.7 KB | view raw |
| ref_keystream.c | reference | degenerate all-zero + zero-key chacha20 references (zerokey mode == RFC 7539 vector) | 3.8 KB | view raw |
| csprng_force_zero.c | trigger-source | root /dev/kmem rig: info/force/check/restore/peekctx/window/window2 on per-CPU csprng_state | 8.3 KB | view raw |
| chain_model.py | reference | offline model of the manufactured window; matches live kernel 4/4 blocks + 4/4 loaded keys | 2.7 KB | view raw |
| boot_probe.sh | run-script | one-shot fresh-boot probe: build, unprivileged reads+scan, kmem state dump at earliest ssh | 1.3 KB | view raw |
| window_experiment.sh | run-script | manufactured-window driver v1 (usched-mask pitfall documented) | 2.3 KB | view raw |
| window_experiment2.sh | run-script | manufactured-window driver v2 (correct :4 pinning) | 3.1 KB | view raw |
| build.sh | build-script | cc -O2 rand_probe + ref_keystream | 151 B | view raw |
| run.sh | run-script | single-boot probe run | 208 B | view raw |
| boot1_probe.txt | run-log | fresh boot 1: full probe (uptime 17-33 s), all states keyed, 0 zero blocks | 3.0 KB | view raw |
| boot2_probe.txt | run-log | fresh boot 2: same | 3.0 KB | view raw |
| boot3_probe.txt | run-log | fresh boot 3: same | 3.0 KB | view raw |
| leak_sample.txt | leak-sample | cross-boot comparison: 59-60/64 B differ on all pairs/interfaces; 0/6624 zero blocks; per-boot state dumps | 3.8 KB | view raw |
| kmem_window_decisive.log | run-log | DECISIVE: sigma offset proof, 5/5 identical manufactured windows (0x00*32 first), mixed-mode mask, self-heal | 4.8 KB | view raw |
| kmem_window_chain.log | run-log | per-read live ctx dumps matching chain_model.py exactly | 2.4 KB | view raw |
| kmem_window_attempt1.log | run-log | attempt 1: usched :3 = {cpu0,cpu1} mis-pin; one background burn observed on state[2] | 7.9 KB | view raw |
| kmem_window_attempt2.log | run-log | attempt 2: usched :2 = cpu1 mis-pin; reads never touched forced state | 3.9 KB | view raw |
| kmem_window_attempt3.log | run-log | attempt 3: correct pin, exec-race burns window (deterministic post-burn chain) | 3.2 KB | view raw |
| final_verify.txt | run-log | end-to-end sequence + result of this pass | 4.9 KB | view raw |
| final_state.log | run-log | guest left healthy: all states keyed, rand_mode=mixed restored | 904 B | view raw |
| env.txt | environment | uname/build, symbols, struct offsets, usched mask semantics, no-rdrand entropy-poor guest | 2.5 KB | view raw |
| fix_apply_compile.log | build-log | fix.diff dry-run + apply + exact-kernel-CFLAGS compiles (pristine + patched objects) + pristine restore | 846 B | view raw |
| fix.diff | suggested-fix | defense-in-depth: gate CSPRNG_UNLIMITED readers too when reseed_cnt==0 (return 0, don't emit degenerate keystream) | 1.2 KB | view raw |
| VERDICT.md | verdict | full narrative: Q1 first-reseed timing, Q2 window content (proven), Q3 unprivileged observability (none), DF-5766 overlap | 6.0 KB | β raw |
| README.md | readme | build/run/expected + cross-boot reproduce procedure | 4.9 KB | β raw |
| archive-2026-07/ | archive | prior July verification evidence (boot probes, leak_sample, env, final_verify, manifest) | 4.0 KB | β download |
DF-0220 β PoC: Predictable RNG pre-reseed claim (verification)
Finding: findings/DF-0220-csprng-predictable-rng-pre-reseed.md
Claim: /dev/urandom, getrandom(2), and kern.random return a deterministic
all-zero-key ChaCha20 keystream before the first Fortuna reseed β identical
across independent boots.
Verdict (re-verified 2026-10-12, VERIFY mode): NOT REPRODUCED. No
unprivileged user can observe the pre-reseed window on this kernel β but the
emit mechanism itself is now PROVEN live and fully deterministic via a
root-manufactured state (/dev/urandom returns literal 0x00 bytes, then a
keystream reproduced offline by chain_model.py, 5/5 identical trials).
See VERDICT.md for the full line-cited trace and the DF-5766 overlap.
Files
| File | Purpose |
|---|---|
rand_probe.c |
Reads 64 B from /dev/urandom, /dev/random, getrandom(2), kern.random + uptime |
zero_scan.c |
Unprivileged early-boot window scanner (3 interfaces, all-zero-block detector) |
ref_keystream.c |
Degenerate + zero-key chacha20 references (zerokey mode validated vs RFC 7539 vector) |
csprng_force_zero.c |
Root /dev/kmem rig: dump/force/check/restore per-CPU csprng state; in-process manufactured window (window, window2 = per-read ctx dumps) |
chain_model.py |
Offline model of the manufactured window β matches live kernel 4/4 blocks + 4/4 keys |
boot_probe.sh |
One-shot fresh-boot probe (build + unprivileged reads + scan + kmem state dump) |
window_experiment.sh / window_experiment2.sh |
Manufactured-window drivers (v1/v2 β see attempt logs for the pinning pitfalls) |
build.sh / run.sh |
Build + single-boot run of the probe pair |
boot{1,2,3}_probe.txt |
Fresh-boot outputs (2026-10-12): 4-source hex + zero_scan + per-CPU state at earliest ssh |
leak_sample.txt |
Cross-boot comparison: all pairs differ 59-60/64 B; 0 zero blocks / 6624 samples |
kmem_window_decisive.log |
THE decisive run: sigma proof, 5/5 identical manufactured windows, mixed-mode mask, self-heal |
kmem_window_chain.log |
Per-read live ctx dumps matching chain_model.py exactly |
kmem_window_attempt{1,2,3}.log |
Earlier attempts (usched-mask pinning pitfalls + background-burn evidence) |
final_verify.txt |
End-to-end re-verification sequence + result |
final_state.log |
Guest left healthy (all states keyed, rand_mode restored) |
env.txt |
Guest uname/build, symbols, struct offsets, usched semantics |
fix_apply_compile.log |
fix.diff: applies cleanly; compiles with exact kernel CFLAGS |
VERDICT.md |
Full narrative verdict |
fix.diff |
Defense-in-depth fix (gate unlimited readers when reseed_cnt==0) |
manifest.json |
Machine-readable artifact catalog |
archive-2026-07/ |
Prior (July) verification evidence, preserved |
Build
./build.sh # rand_probe + ref_keystream cc -O2 -o zero_scan zero_scan.c cc -O2 -o csprng_force_zero csprng_force_zero # root tool
Run
./run.sh # single-boot probe (unprivileged) ./boot_probe.sh # full fresh-boot probe (root; su's down to maxx) usched :4 ./csprng_force_zero <sym> window 2 # manufactured window (root, csprng mode) python3 chain_model.py # offline model -> matches the above
(sym = nm /boot/kernel/kernel | awk '/ csprng_pcpu$/ {print "0x"$1}')
Expected (claim TRUE / bug present)
Across independent fresh boots the 64-byte outputs would be byte-for-byte
identical AND identical to the degenerate reference; zero_scan would see
all-zero blocks; rand_mode=csprng would return 0x00 bytes.
Actual (DragonFly 6.5-DEVELOPMENT #0, 2026-10-12)
Three fresh vm.sh reset with-src boots: outputs differ 59-60/64 bytes on
every interface and every boot pair, never all-zero; 0/6624 all-zero blocks
in the first 33 s of boot; every per-CPU state already keyed (reseed_cnt>=1)
at the earliest ssh. The window CAN be manufactured with root /dev/kmem β
and then behaves exactly as the finding predicted (deterministic 0x00β¦
chain) β but the precondition (reseed_cnt==0 β§ pool[0]<96 at an UNLIMITED
read) is unreachable from userspace because rand_initialize() pre-feeds
every state's pool[0] with ~15 KB before its first possible read, and the
reseed attempt precedes the emit loop. See VERDICT.md.
Reproduce across boots (the decisive test)
for N in 1 2 3; do
dfbsd-qemu/vm.sh reset with-src
scp -F dfbsd-qemu/config {rand_probe,ref_keystream,zero_scan,csprng_force_zero}.c boot_probe.sh dfbsd:/tmp/rng220/
dfbsd-qemu/vm.sh run_root 'sh /tmp/rng220/boot_probe.sh' > boot${N}_probe.txt
done
# then compare the hex sections (see leak_sample.txt) β they must differ
DF-0220 VERDICT -- not_reproduced (impact: none; confidence: certain)
Bottom line
NOT REPRODUCED (3rd verification, 2026-10-12, source unchanged since 2026-06-27). No unprivileged user can observe the pre-reseed zero-key window on this kernel: rand_initialize() (kern_nrandom.c:485-560, SYSINIT SI_BOOT2_POST_SMP :562, before init(8)) pre-feeds EVERY per-CPU state's pool[0] with ~15 KB (sizeof(struct globaldata)=14976 B via RAND_SRC_THREAD2 :539-543), and csprng_get_random() always attempts a reseed BEFORE its emit loop (subr_csprng.c:134-149 precede :151-166) with ratecheck's {0,0} first-call guarantee (kern_time.c:1153-1157) -- so each state's first read keys the cipher before emitting a byte. Live proof: on 3 fresh boots every state shows reseed_cnt>=1/key!=0 at the earliest ssh (uptime 17-33 s); the BSP is frozen at reseed_cnt=1 with last_reseed=184467440735.6xxxxx (the garbage getmicrouptime captured AT its rand_initialize-time reseed, = DF-5766's frozen baseline -- pinning the first reseed pre-userland). Cross-boot outputs differ 59-60/64 bytes on all interfaces/pairs; 0 all-zero blocks in 6624 unprivileged early-boot samples (urandom+getrandom+kern.random); default mixed mode additionally XORs IBAA (kern_nrandom.c:708-713), masking even a manufactured window. NEW this pass: the mechanism itself is now PROVEN live and fully deterministic -- with a root /dev/kmem-manufactured state (reseed_cnt=0 AND key=0 AND ctx=0 AND pool[0].bytes=0 AND last_reseed={0,0}), /dev/urandom in rand_mode=csprng returns literal 0x00*32 and then a keystream chain_model.py reproduces offline exactly (4/4 blocks + 4/4 internally-loaded keys, 5/5 byte-identical trials; per-read live ctx dumps match). The manufactured window is consumed by the first background reader within ~0.2 s and self-heals via reseed within seconds of pool[0] refilling -- it is unreachable and unstable, never userspace-observable. Overlap with DF-5766: DF-5766's frozen-BSP bug is what makes the BSP's reseed_cnt permanently 1 (never 0), reinforcing closure; the entropy quality of the BSP's single forever-key is DF-5766's subject, not DF-0220's zero-key window.
Reproduce
build: cc -O2 -o rand_probe rand_probe.c && cc -O2 -o ref_keystream ref_keystream.c && cc -O2 -o zero_scan zero_scan.c && cc -O2 -o csprng_force_zero csprng_force_zero.c
run: sh boot_probe.sh (per fresh vm.sh reset with-src boot, 3x); SYM=0x$(nm /boot/kernel/kernel | awk '/ csprng_pcpu$/ {print $1}'); sysctl kern.rand_mode=csprng; usched :4 ./csprng_force_zero $SYM window 2 (x5); python3 chain_model.py
Evidence in this pack
kmem_window_decisive.log (THE decisive run: sigma-at-offset-40 proof; 5/5 identical manufactured windows with blk1=0x00*32; mixed-mode non-zero; self-heal pool0 0->372 -> reseed_cnt 1); kmem_window_chain.log + chain_model.py (per-read live ctx dumps equal the offline model 4/4 blocks + 4/4 keys 523b05d3/4b57cfe4/8b5a7c87/22589bb5); boot{1,2,3}_probe.txt (fresh boots: unprivileged 4-source reads + zero_scan 0/6624 zero blocks + all 6 states keyed at earliest ssh, BSP frozen at reseed_cnt=1 with 184467440735.6xx); leak_sample.txt (cross-boot pairwise 59-60/64 differ, never all-zero); final_verify.txt (full sequence); fix_apply_compile.log (applies @138 + compiles with exact kernel CFLAGS); env.txt; VERDICT.md
Kernel references
sys/kern/subr_csprng.c:84sys/kern/subr_csprng.c:85sys/kern/subr_csprng.c:134sys/kern/subr_csprng.c:137sys/kern/subr_csprng.c:146sys/kern/subr_csprng.c:151sys/kern/subr_csprng.c:155sys/kern/subr_csprng.c:158sys/kern/subr_csprng.c:176sys/kern/subr_csprng.c:188sys/kern/subr_csprng.c:231sys/kern/subr_csprng.c:272sys/kern/kern_nrandom.c:485sys/kern/kern_nrandom.c:495sys/kern/kern_nrandom.c:539sys/kern/kern_nrandom.c:543sys/kern/kern_nrandom.c:558sys/kern/kern_nrandom.c:562sys/kern/kern_nrandom.c:697sys/kern/kern_nrandom.c:708sys/kern/kern_nrandom.c:739sys/kern/kern_nrandom.c:770sys/kern/kern_time.c:1153sys/kern/kern_memio.c:352sys/platform/pc64/x86_64/machdep.c:2777sys/sys/kernel.h:168sys/sys/kernel.h:216sys/crypto/chacha20/chacha.c:56
Fix validation (A/B)
Defense-in-depth only (window already closed by the rgd pool-pre-feed): fix.diff gates CSPRNG_UNLIMITED readers too when reseed_cnt==0, returning 0 bytes instead of emitting the degenerate keystream (mirrors getrandom(GRND_NONBLOCK)); applies cleanly (hunk @138) and compiles warning-free with exact kernel CFLAGS (fix_apply_compile.log).
Baseline vs post-fix behavior is in run.log / run_fixed*.log (userspace) or panic.txt/hang.txt vs run_fixed_kernel.log (kernel); the identical rig ran both sides.
poc_changes
Added csprng_force_zero.c (root /dev/kmem rig: info/force/check/restore/peekctx/window/window2 -- force manufactures the exact precondition tuple reseed_cnt=0 ^ key=0 ^ ctx=0 ^ pool0.bytes=0 ^ last_reseed={0,0}), zero_scan.c (unprivileged 3-interface window scanner; fixed missing sys/random.h include flagged by guest cc), chain_model.py (offline model of the degenerate emit chain, validated 4/4 blocks + 4/4 keys vs live ctx dumps), boot_probe.sh + window_experiment{,2}.sh drivers, and 6 new evidence logs. ref_keystream.c gained a zerokey mode (validated against the RFC 7539 zero-key vector 76b8e0ad...). rand_probe.c unchanged. Key rig lesson: usched :N is a CPU MASK (:4=cpu2), not an index -- attempts 1-2 mis-pinned; and a separate-process read loses the manufactured window to a background consumer within ~0.2 s, so the window action forces+reads in-process.
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 Attempts: 8 Runtime: ~2400s
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
not_testableNot applicable: no reproduced behavior exists to contrast. The finding is not reproducible from userspace; a patched kernel behaves identically for every observable workload (reseed_cnt>0 before any userspace read in both variants). The fix was validated to the maximum meaningful extent: patch -p1 --dry-run clean (hunk @138), patched TU compiles with the exact kernel build CFLAGS into a distinct object (64df3fc0... vs pristine dac9843d..., zero warnings), tree restored pristine afterwards. fix.diff stands as hardening against future regressions of the pool-pre-feed invariant (e.g. if the RAND_SRC_THREAD2 rgd feed or pool routing ever changes).
fix_apply_compile.log (dry-run + apply + exact-kernel-CFLAGS compiles of pristine and patched TUs + pristine-restore md5); fix.diff
Confirmed kernel references
- sys/kern/subr_csprng.c:84
- sys/kern/subr_csprng.c:85
- sys/kern/subr_csprng.c:134
- sys/kern/subr_csprng.c:137
- sys/kern/subr_csprng.c:146
- sys/kern/subr_csprng.c:151
- sys/kern/subr_csprng.c:155
- sys/kern/subr_csprng.c:158
- sys/kern/subr_csprng.c:176
- sys/kern/subr_csprng.c:188
- sys/kern/subr_csprng.c:231
- sys/kern/subr_csprng.c:272
- sys/kern/kern_nrandom.c:485
- sys/kern/kern_nrandom.c:495
- sys/kern/kern_nrandom.c:539
- sys/kern/kern_nrandom.c:543
- sys/kern/kern_nrandom.c:558
- sys/kern/kern_nrandom.c:562
- sys/kern/kern_nrandom.c:697
- sys/kern/kern_nrandom.c:708
- sys/kern/kern_nrandom.c:739
- sys/kern/kern_nrandom.c:770
- sys/kern/kern_time.c:1153
- sys/kern/kern_memio.c:352
- sys/platform/pc64/x86_64/machdep.c:2777
- sys/sys/kernel.h:168
- sys/sys/kernel.h:216
- sys/crypto/chacha20/chacha.c:56
Detail
Evidence (decisive lines)
kmem_window_decisive.log (THE decisive run: sigma-at-offset-40 proof; 5/5 identical manufactured windows with blk1=0x00*32; mixed-mode non-zero; self-heal pool0 0->372 -> reseed_cnt 1); kmem_window_chain.log + chain_model.py (per-read live ctx dumps equal the offline model 4/4 blocks + 4/4 keys 523b05d3/4b57cfe4/8b5a7c87/22589bb5); boot{1,2,3}_probe.txt (fresh boots: unprivileged 4-source reads + zero_scan 0/6624 zero blocks + all 6 states keyed at earliest ssh, BSP frozen at reseed_cnt=1 with 184467440735.6xx); leak_sample.txt (cross-boot pairwise 59-60/64 differ, never all-zero); final_verify.txt (full sequence); fix_apply_compile.log (applies @138 + compiles with exact kernel CFLAGS); env.txt; VERDICT.md
PoC changes
Added csprng_force_zero.c (root /dev/kmem rig: info/force/check/restore/peekctx/window/window2 -- force manufactures the exact precondition tuple reseed_cnt=0 ^ key=0 ^ ctx=0 ^ pool0.bytes=0 ^ last_reseed={0,0}), zero_scan.c (unprivileged 3-interface window scanner; fixed missing sys/random.h include flagged by guest cc), chain_model.py (offline model of the degenerate emit chain, validated 4/4 blocks + 4/4 keys vs live ctx dumps), boot_probe.sh + window_experiment{,2}.sh drivers, and 6 new evidence logs. ref_keystream.c gained a zerokey mode (validated against the RFC 7539 zero-key vector 76b8e0ad...). rand_probe.c unchanged. Key rig lesson: usched :N is a CPU MASK (:4=cpu2), not an index -- attempts 1-2 mis-pinned; and a separate-process read loses the manufactured window to a background consumer within ~0.2 s, so the window action forces+reads in-process.
Verified recommended fix
Defense-in-depth only (window already closed by the rgd pool-pre-feed): fix.diff gates CSPRNG_UNLIMITED readers too when reseed_cnt==0, returning 0 bytes instead of emitting the degenerate keystream (mirrors getrandom(GRND_NONBLOCK)); applies cleanly (hunk @138) and compiles warning-free with exact kernel CFLAGS (fix_apply_compile.log).
Verdict
NOT REPRODUCED (3rd verification, 2026-10-12, source unchanged since 2026-06-27). No unprivileged user can observe the pre-reseed zero-key window on this kernel: rand_initialize() (kern_nrandom.c:485-560, SYSINIT SI_BOOT2_POST_SMP :562, before init(8)) pre-feeds EVERY per-CPU state's pool[0] with ~15 KB (sizeof(struct globaldata)=14976 B via RAND_SRC_THREAD2 :539-543), and csprng_get_random() always attempts a reseed BEFORE its emit loop (subr_csprng.c:134-149 precede :151-166) with ratecheck's {0,0} first-call guarantee (kern_time.c:1153-1157) -- so each state's first read keys the cipher before emitting a byte. Live proof: on 3 fresh boots every state shows reseed_cnt>=1/key!=0 at the earliest ssh (uptime 17-33 s); the BSP is frozen at reseed_cnt=1 with last_reseed=184467440735.6xxxxx (the garbage getmicrouptime captured AT its rand_initialize-time reseed, = DF-5766's frozen baseline -- pinning the first reseed pre-userland). Cross-boot outputs differ 59-60/64 bytes on all interfaces/pairs; 0 all-zero blocks in 6624 unprivileged early-boot samples (urandom+getrandom+kern.random); default mixed mode additionally XORs IBAA (kern_nrandom.c:708-713), masking even a manufactured window. NEW this pass: the mechanism itself is now PROVEN live and fully deterministic -- with a root /dev/kmem-manufactured state (reseed_cnt=0 AND key=0 AND ctx=0 AND pool[0].bytes=0 AND last_reseed={0,0}), /dev/urandom in rand_mode=csprng returns literal 0x00*32 and then a keystream chain_model.py reproduces offline exactly (4/4 blocks + 4/4 internally-loaded keys, 5/5 byte-identical trials; per-read live ctx dumps match). The manufactured window is consumed by the first background reader within ~0.2 s and self-heals via reseed within seconds of pool[0] refilling -- it is unreachable and unstable, never userspace-observable. Overlap with DF-5766: DF-5766's frozen-BSP bug is what makes the BSP's reseed_cnt permanently 1 (never 0), reinforcing closure; the entropy quality of the BSP's single forever-key is DF-5766's subject, not DF-0220's zero-key window.
No comments yet.