# DF-5708 — `skrandom()` accepts out-of-domain seeds; MINSTD state has absorbing fixed points

**File audited (pass 2):** `sys/libkern/random.c`
**Class:** PRNG domain violation / degenerate fixed point (CWE-670)
**Severity:** Low · **Confidence:** certain (math + live kernel demonstration)

## The defect

`skrandom()` (random.c:38-42) stores any caller-supplied `u_long` verbatim
into `randseed`. `krandom()`'s Schrage step (random.c:60-66) computes
`t = 16807*lo - 2836*hi`, and on `t <= 0` adds 2^31-1 **once**. For any
state `x ≡ 0 (mod 2^31-1)` — i.e. x ∈ {0, m, 2m, ...} for m = 2147483647 —
the step computes exactly `t = 0`, the fixup yields `t = m = 0x7fffffff`,
and from state `m` the step again computes 0 → m: **an absorbing fixed
point. Every subsequent krandom() call returns the constant 0x7fffffff
for the lifetime of the boot.**

Reachable trigger: `sys/dev/netif/oce/oce_mbox.c:791` calls
`skrandom(karc4random())` — a full 32-bit RC4 word — so with probability
3/2^32 per oce attach the drawn seed is 0, m, or 2m and the kernel PRNG
is permanently bricked on real (Emulex 10Gb) hardware. The hpt RAID
drivers guard only against the 0 *output* (`do { stamp = krandom(); }
while (stamp==0);` — hpt27xx_os_bsd.c:221, hptmv/entry.c:2552,
hptrr_os_bsd.c:203) and not against the fixed point.

Constant-output consequences (by code path, each consumer's output becomes
a known constant): identical inode generation numbers on every fresh
allocation (NFS stale-filehandle confusion — DF-2250 impact-1 via a
different root cause), identical CHAP challenge words, identical NAT alias
ports, identical LCP magics.

Secondary (documented, currently unreachable): for seeds ≥ ~2^38 the
single `+= m` fixup under-corrects and `krandom()` returns negative
values sign-extended into u_long, violating the documented
"[0, 2^31-1]" range (random.c:47); all current callers supply < 2^32 so
this is a robustness note only.

## Live proof (fixedpoint.log, DragonFly 6.5-DEVELOPMENT guest)

    1) write seed=0:            after 8s:  0x7fffffff   (stuck)
       after 16s:               0x7fffffff
    2) write seed=0xFFFFFFFE (=2m): after 8s: 0x7fffffff (one draw, then stuck)
    3) control seed=0x12345:    after 8s:  0x46183131   (8 draws, evolving)

Writes are the root-equivalent of `skrandom(0)` / `skrandom(2m)` via
/dev/mem at the `randseed` symbol; the draws in between are the six
per-CPU loadav callouts (kern_synch.c:1431, measured 1.23/s).

## Build / run

    cc -O2 -o devmem devmem.c && cc -O2 -o minstd minstd.c
    RS=0x$(nm /boot/kernel/kernel | grep -w randseed | cut -d' ' -f1)
    ./devmem w $RS 0; sleep 8; ./devmem r $RS   # -> 0x7fffffff forever
    ./devmem w $RS 0xFFFFFFFE; sleep 8; ./devmem r $RS

## Fix

`fix.diff`: fold the seed into [1, 2^31-2] in `skrandom()` and add a
defensive re-seed in `krandom()` when the state is outside the domain.
