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

Fully reversible non-cryptographic MINSTD PRNG is sole source of randomness for security-sensitive kernel values

Summary

krandom() implements Park-Miller MINSTD LCG (random.c:55-66) whose entire internal state IS the output: given one output value v next is deterministically computable state only 31 bits. Seeded once at boot with low-entropy tv_sec^tv_nsec (init_main.c:497 carries XXX comment). Despite being documented for randomizing profiling clock numerous security-sensitive consumers use as sole entropy source making values predictable to any observer who can see one output. MINSTD is x[n+1]=16807*x[n] mod (2^31-1) output==state trivially invertible (16807 has modular inverse mod 2^31-1 = 1407677000). Security-sensitive consumers: if_spppsubr.c:4265-4268 (CHAP authentication challenge = seed^krandom() over 4 consecutive fully-correlated outputs) ffs_alloc.c:642/ffs_vfsops.c:1175/ext2_vfsops.c:1421 (inode generation number i_gen for NFS filehandle unguessability) frag6.c:107 (IPv6 fragment ID ip6_id) nfsm_subs.c:159 (NFS RPC XID) if_spppsubr.c:1251/2527/2709 (PPP LCP magic/loop detection) ip_fw3_nat.c:439 (NAT alias port) ip6_input.c:184/186+in6_ifattach.c:145/189 (IPv6 privacy-address/flow). Remote unauthenticated where consumer network-facing: CHAP active attacker observes one challenge word recovers MINSTD state predicts all future challenges defeating auth-response. IPv6 fragment ID prediction enables fragment-injection/packet-assembly attacks. NFS filehandle prediction i_gen guessable filehandles feasible breaking export security boundary. NAT port prediction aids connection-hijacking. [VERIFIED pass2: reproduced - one observed output yields exact forward prediction 1000/1000 plus backward recovery; live on-wire NFS XIDs proven perfectly linear +1 via lo0 capture; i_gen consumer claims honestly downgraded to legacy fallbacks ext2 already karc4random ffs newfs-FSIRAND; fix-validated consumer re-pointing to karc4random with NFS functional; NOT privesc]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2251 Β· 18 files
FileTypeDescriptionSize
rng_harness.c β€” 10.9 KB view raw
nfsfh.c β€” 8.2 KB view raw
analyze_gen.py β€” 1.9 KB view raw
build.sh β€” 96 B view raw
run.sh β€” 90 B view raw
build.log β€” 5.6 MB ↓ download
install.log β€” 80.4 KB view raw
run.log β€” 4.1 KB view raw
xid_baseline.cap β€” 27.0 KB ↓ download
xid_fixed.cap β€” 1.2 KB ↓ download
fix_run.log β€” 1.1 KB view raw
ext2_fh.txt β€” 881 B view raw
fh_burst.txt β€” 898 B view raw
fh_probe1.txt β€” 590 B view raw
ext2_debugfs_gens.txt β€” 977 B view raw
fix.diff β€” 5.3 KB view raw
fix_build.log β€” 5.6 MB ↓ download
env.txt β€” 1.2 KB view raw

Fix verification

fixed
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Sat Oct 17 15:43:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

observer sees one krandom-derived value (e.g. one NFS reply XID, one CHAP challenge word pair, one legacy-image i_gen) -> state is the value itself (or 2 candidates after /2+1) -> all future and past outputs computable -> CHAP XOR differences leak the state even under the strong read_random mask; demonstrated end-to-end for XID (+1 disclosure) and in-harness for the LCG inversion

Evidence (decisive lines)

run.log RNGPRED lines (forward/backward/1000-of-1000/i_gen candidate recovery/chap-diff state recovery all exact); xid_baseline.cap vs xid_fixed.cap with fix_run.log (XIDs +1 linear baseline, random fixed); ext2_debugfs_gens.txt + ext2_fh.txt (gens exceed 2^31, match karc4random not krandom); VERDICT.md documents the newfs FSIRAND and ext2_alloc.c:449 evidence

PoC changes

authored from scratch: kernel harness prediction sysctl debug.rng_pred; nfsfh.c raw ONC-RPC observer needed three fixes during bring-up (RPC reply header offset 24 not 16, AUTH_SYS credentials plus a reserved source port for mountd, LE fh layout with gen at bytes 24..28); the planned ffs i_gen end-to-end was replaced after on-guest evidence showed fresh-newfs ffs and ext2 do not draw krandom for new inodes

Verified recommended fix

re-point the security-sensitive consumers (sppp CHAP/LCP, ffs/ext2 i_gen fallbacks, frag6 + per-fragment id, NFS XID, ip6 flow/desync/privacy seeds, NAT alias ports) from krandom() to karc4random()/karc4random_buf() per fix.diff

Verdict

MINSTD reversibility confirmed on the real krandom(): from one observed output the harness reproduced the next 7 (multiplier 16807), recovered the previous 7 (inverse 1407677000), and predicted the NEXT 1000 real draws 1000/1000 exactly; the ffs i_gen mapping krandom()/2+1 leaves only 2 candidate states, verified by predicting the next generation; the sppp CHAP XOR words leak the global PRNG state because ch[i]^ch[j]=v_i^v_j cancels the strong read_random seed (state recovered exactly via a 2^31 offline search, executed in-harness). Live wire consumer: NFS RPC XIDs are a perfectly linear +1 counter after a single krandom seed (captured on lo0: 0x402382e6,e7,e8,e9,ea); IPv6 frag IDs have the same shape (ip6_id++, reasoned line-accurate after jumbo echo was refused on lo0). Consumer claims honestly re-assessed against primary sources: ext2 new-inode gens already use karc4random (ext2_alloc.c:449, ground truth in ext2_debugfs_gens.txt) and ffs new-inode gens come from newfs FSIRAND userland random plus ++ (mkfs.c:836/1140), so the krandom i_gen paths are legacy fallbacks; the genuinely krandom-fed live consumers are CHAP words, LCP magic, ip6_id/nfs_xid seeds, NAT alias ports, IPv6 privacy seeds. Fix (consumer re-point to karc4random/karc4random_buf across 10 files) compiled clean and was validated on the wire: NFS XIDs became random 32-bit values with NFS fully functional. Impact classified as leak = full disclosure of kernel PRNG state/stream from one observed value; not kernel-memory disclosure, not memory corruption, no privesc chain (closed per directive).