================================================================================ DF-0220 — Cross-boot RNG keystream comparison (the decisive reproducibility test) ================================================================================ The finding claims: "/dev/urandom + getrandom + kern.random return a deterministic ChaCha20 keystream (zero key) before first reseed — byte-for-byte reproducible sequence identical on every booted machine." Test method: vm.sh reset to a pristine snapshot (clean-install), boot, read 64 bytes from every RNG interface as soon as ssh comes up. Repeat on a 2nd fresh boot. Compare byte-for-byte. Also compare against the reference "pre-reseed degenerate" keystream computed by ref_keystream.c. -------------------------------------------------------------------------------- REFERENCE: the degenerate pre-reseed csprng keystream (ref_keystream.c output) -------------------------------------------------------------------------------- This is what /dev/urandom WOULD return (csprng-only) if reseed had not happened. It is computed by running the kernel's exact chacha20 (CHACHA_NONCE0_CTR128 + KEYSTREAM_ONLY) on the all-zero cipher context that csprng_init() leaves when chacha_keysetup() is never called. NOTE: all-zero input is a FIXED POINT of the chacha quarterround (0+0=0, 0^0=0, rotl(0,n)=0), so the "keystream" is all zeros. ref_preseed_keystream (64 bytes, all-zero chacha input[16]): 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (The finding described this as "ChaCha20(key=0^32, counter=0^16)" — that is inaccurate: with keysetup never called, input[0..3] (the sigma constants) are ALSO zero, so the real degenerate output is all-zeros, not standard zero-key chacha20 which would be non-zero.) -------------------------------------------------------------------------------- BOOT #1 (fresh vm.sh reset; uptime at read = 58.07 s) — rand_mode=mixed (default) -------------------------------------------------------------------------------- /dev/urandom (64 bytes): 0000: c5 4e ac 5a fe 38 5b 27 4e cd e7 8b 2c b9 c6 8b 0010: 46 9a a4 61 09 13 8e 9c 6a af e0 27 54 28 47 0d 0020: f0 5f 01 dc 2f 11 6e dd 65 aa 47 e3 d7 66 d2 6e 0030: 83 c8 05 3f 77 d1 da 3e 7c cb 3c 87 97 24 e1 ee getrandom(2) (64 bytes): 0000: 55 77 05 2f c0 ba 8b bc f8 36 aa 66 28 a6 ec 94 0010: 21 02 a3 b2 fb 6a 74 86 e3 80 7c 99 18 19 d2 09 0020: c7 41 df 50 02 96 e7 8d b5 8a 46 f8 04 97 b2 69 0030: 14 2a bf ce 5a db 17 c5 27 62 97 f8 a6 4d 99 31 kern.random sysctl (64 bytes): 0000: a1 4f e3 a0 fa ee 06 56 41 7d 02 a8 b9 73 f5 0e 0010: d8 1b 0e a5 25 17 db 95 3c e0 ac 9d 67 d0 6d 99 0020: 0d f5 5b 40 25 1c 7a da 85 be 37 88 bf 67 68 9e 0030: f0 97 f5 7d a6 fd d0 4b ed 26 53 8d 48 cb 16 65 -------------------------------------------------------------------------------- BOOT #2 (fresh vm.sh reset; uptime at read = 17.86 s) — rand_mode=mixed (default) -------------------------------------------------------------------------------- /dev/urandom (64 bytes): 0000: ad 5b ad fa f4 ec 46 8f a1 23 fd 94 1c 6e e1 bf 0010: 35 3b 60 b4 e4 c6 27 e7 3f a6 46 e1 6a 43 28 7f 0020: 6e 82 7e c8 c7 0a 3a be ea 34 85 d6 38 c2 4f 40 0030: 54 b7 9e 1b c9 6b 5b fa ad 6a 08 cb 3b 37 f0 f6 getrandom(2) (64 bytes): 0000: d6 18 88 3f b2 5e ed 19 f4 fd 23 f4 00 9c 9b 84 0010: f5 81 e2 9c 98 4f 7e 82 6c c0 37 44 45 73 ca 88 0020: a4 0b cb 72 79 10 9e fb 10 50 53 0a 43 43 65 f8 0030: 34 51 af af 87 b5 e0 6d d0 a3 63 8b 21 41 a6 00 kern.random sysctl (64 bytes): 0000: 22 eb bb a8 d9 75 33 68 fd b1 be 36 5a 5d ad e8 0010: 62 c9 45 85 80 04 af 65 d1 da 10 2e 31 70 ca 3e 0020: 07 43 90 61 43 97 93 0e 9b 00 ca 63 55 fa a1 e2 0030: 4a c2 7f b4 49 e1 fb 2e 9f 2c d8 56 99 33 5e a9 -------------------------------------------------------------------------------- COMPARISON RESULT -------------------------------------------------------------------------------- Boot #1 /dev/urandom vs Boot #2 /dev/urandom : 64/64 bytes differ (0 match) Boot #1 getrandom vs Boot #2 getrandom : 64/64 bytes differ (0 match) Boot #1 kern.random vs Boot #2 kern.random : 64/64 bytes differ (0 match) Either boot vs reference (all-zero) : 64/64 bytes differ (0 match) => The kernel RNG output is NOT deterministic across boots on this kernel, and it NEVER matches the degenerate all-zero pre-reseed keystream. The finding's reproducibility claim is REFUTED on DragonFly master DEV (6.5-DEVELOPMENT v6.5.0.1712.g89e6a). -------------------------------------------------------------------------------- WHY: csprng-only mode (rand_mode=csprng) is non-zero -> cipher was keyed pre-userspace -------------------------------------------------------------------------------- If reseed had NOT happened before userspace, rand_mode=csprng (which returns the raw csprng stream with no IBAA mixing) would emit the all-zero fixed-point keystream above. Three consecutive reads with rand_mode=csprng on a live boot: run1: 26 a9 d4 b6 7c fd 9c ac f3 9d 8d d2 d6 03 90 0c ... run2: 8a 9c d7 26 30 df 92 1f eb 00 d4 ef 6e 26 82 c3 ... run3: 59 5b 4e 6b 8a 11 5d 6a 13 a4 47 c9 c5 90 4e da ... All non-zero, all distinct -> the csprng cipher context is properly keyed (i.e. csprng_reseed() has run) BEFORE any userspace read reaches it. ROOT CAUSE OF NON-REPRODUCTION: rand_initialize() (kern_nrandom.c:488-560, SYSINIT SI_BOOT2_POST_SMP) feeds sizeof(struct globaldata) — thousands of bytes, see gd_reserved02B[200] alone = 1600 B — into csprng pool[0] via the RAND_SRC_THREAD2 injection at kern_nrandom.c:539-543. pool[0] therefore far exceeds MIN_POOL_SIZE (96 bytes, subr_csprng.c:54,188) during rand_initialize, so the first read_random() at kern_nrandom.c:558 triggers a SUCCESSFUL csprng_reseed() (subr_csprng.c:176-238), keying the cipher before init(8) runs. The finding's premise that "pool[0] commonly receives < 96 bytes during early boot" omits this per-CPU globaldata feed entirely.