DF-0220 / leak_sample.txt
================================================================================
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.