⬢ DragonFlyBSD Kernel Audit
DF-0220 / chain_model.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""DF-0220 — verified model of the manufactured pre-reseed window output.

Reproduces, byte-for-byte, what /dev/urandom returns from a per-CPU
csprng_state forced into the pre-reseed condition
(reseed_cnt=0, key=0^32, cipher_ctx=0^64, pool[0].bytes=0) in
rand_mode=csprng, for consecutive 32-byte reads:

  read N:  emit    = block(cur_state, ctr)[0:32]        ctr += 1
           keygen  = block(cur_state, ctr)[0:32] -> K   ctr += 1
           keysetup: cur_state = sigma || K || ctr (ctr preserved)

Key detail: on the FIRST read the ctx is fully zero (chacha_keysetup was
never called), so input[0..3] are ZERO (no sigma).  The block at ctr=0 is
the all-zero fixed point -> blk1 = 0x00*32.  The keygen at ctr=1 then runs
with sigma STILL absent (keysetup runs after keygen), so K1 comes from the
sigma-less zero state at ctr=1 -- the chain never becomes plain
"chacha20(key=0)".

Verified against the live kernel (DragonFly 6.5-DEVELOPMENT #0, Jul 2 2026
build): 5/5 identical trials; per-read live ctx dumps (kmem) match the
model's sigma/key/counter after every read.  See kmem_window_decisive.log
and kmem_window_chain.log.

Run: python3 chain_model.py
"""
def rotl(v, c): return ((v << c) & 0xffffffff) | (v >> (32 - c))
def qr(x, a, b, c, d):
    x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rotl(x[d] ^ x[a], 16)
    x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rotl(x[b] ^ x[c], 12)
    x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rotl(x[d] ^ x[a], 8)
    x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rotl(x[b] ^ x[c], 7)
def block(st):
    st = list(st); w = st[:]
    for _ in range(10):
        qr(w,0,4,8,12); qr(w,1,5,9,13); qr(w,2,6,10,14); qr(w,3,7,11,15)
        qr(w,0,5,10,15); qr(w,1,6,11,12); qr(w,2,7,8,13); qr(w,3,4,9,14)
    return b''.join(((w[i]+st[i]) & 0xffffffff).to_bytes(4,'little') for i in range(16))
def words(b): return [int.from_bytes(b[i*4:i*4+4], 'little') for i in range(len(b)//4)]
SIG = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]

st = [0]*16          # csprng_init() leaves cipher_ctx all-zero
ctr = 0
for n in range(4):
    s = list(st); s[12] = ctr
    emit = block(s)[0:32]
    s = list(st); s[12] = ctr + 1
    K = words(block(s)[0:32])
    ctr += 2
    st = SIG + K + [0]*4   # chacha_keysetup writes sigma+key, keeps counter
    print(f"blk{n+1}: {emit.hex()}")
    print(f"  K{n+1} loaded: {b''.join(x.to_bytes(4,'little') for x in K)[:16].hex()} ctr={ctr}")

# Observed on the live kernel (must match):
EXPECT = [
    "00"*32,
    "28958d01c7096dd755434108856438916ed176e0933b02e9268dba09c6bdfb66",
    "8f57ab7001763a0404a36d4ba6e5e1a1a6b1a9ae7680e47a05c5aa365cdcfac4",
    "75aa5968da092cf627267b697a27c651e3688fa22d366243b3a77833b37b27b4",
]