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)
PoC verification
Evidence pack
findings/poc/DF-2251 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedConfirmed kernel references
- sys/libkern/random.c:55
- sys/libkern/random.c:60
- sys/libkern/random.c:66
- sys/kern/init_main.c:497
- sys/net/sppp/if_spppsubr.c:4265
- sys/vfs/ufs/ffs_alloc.c:642
- sys/vfs/ufs/ffs_vfsops.c:1175
- sys/vfs/ext2fs/ext2_vfsops.c:1421
- sys/vfs/ext2fs/ext2_alloc.c:449
- sbin/newfs/mkfs.c:836
- sys/netinet6/frag6.c:107
- sys/netinet6/ip6_output.c:753
- sys/vfs/nfs/nfsm_subs.c:159
- sys/vfs/nfs/nfsm_subs.c:162
- sys/net/ipfw3_nat/ip_fw3_nat.c:439
- sys/vfs/ufs/ufs_vfsops.c:217
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).
No comments yet.