# DF-3568 — rijndael_set_key performs no keyBits validation; invalid bits ⇒ OOB key read + wild round-key walk (hardening)

**File:** `sys/crypto/rijndael/rijndael-api.c:38-43` (wrapper, `void` return, no check)
chained into `sys/crypto/rijndael/rijndael-alg-fst.c:734-815` (only a KASSERT at :738)
and `:951-979` / `:1132-1160` (encrypt/decrypt `for(;;)` loop breaks only on `--r == 0`).

**Class:** CWE-125 (key overread) + CWE-1284 (unvalidated crypto parameter), demonstrated
as a wild OOB read loop on non-INVARIANTS kernels.
**Reachability today:** NONE from unprivileged input — both consumers validate before
calling (`sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:134-141` requires
wk_keylen == 16; `sys/crypto/cryptoapi/cryptoapi.c:308-320` switch-validates 128/192/256,
XTS halves validated at :389-403). The API contract itself is the hazard: a future caller
that passes an unvalidated bits value gets, on a production (non-INVARIANTS) kernel:
1. reads of key[0..31] regardless of keyBits (up to 24 bytes past an 8-byte key), then
2. `ctx->Nr = 0` (silent), then
3. `rijndael_encrypt` walks the round-key pointer forward forever
   (`r = Nr>>1 = 0`, `--r` → -1, never 0) until it hits unmapped memory — panic-class.
**Severity:** Info (hardening) — no current trigger path; behavior chain proven on-guest.

## Reproduce (in-guest, same-source userland-object pattern)

`-DNDEBUG` makes the userland `KASSERT=assert` (alg-fst.c:36) a no-op — exactly the
non-INVARIANTS production kernel semantics.

```
build:  cc -O2 -DNDEBUG -I /usr/src/sys -o guard_rij_badbits \
            rij/guard_rij_badbits.c /usr/src/sys/crypto/rijndael/rijndael-api.c \
            /usr/src/sys/crypto/rijndael/rijndael-alg-fst.c
run:    ./guard_rij_badbits guard      # 8-byte key at page edge, bits=64
        ./guard_rij_badbits safe       # 32-byte key, bits=64 → wild walk demo
```

## Expected output (baseline)

```
$ ./guard_rij_badbits guard
calling rijndael_set_key(&ctx, key8=0x800476ff8, bits=64) (guard page at 0x800477000)
FAULT-CONFIRMED: sig=11 si_addr=0x800477000 (expected key+8 on guard page)
exit=42                                    # first OOB byte load at key+8 (rk[2] load)

$ ./guard_rij_badbits safe
Segmentation fault (core dumped)           # rijndael_encrypt Nr=0 wild walk
exit=139
```

si_addr == key+8 ⇒ key material is read past keyBits/8 (the rk[0..3] loads at
alg-fst.c:740-743 run before any keyBits branch). The `safe`-mode segfault proves the
Nr=0 wild round-key walk (would be a kernel panic in-kernel).

## Expected output (with fix.diff applied to a copy of the source)

```
$ ./guard_rij_fixed guard
BUG-NOT-PRESENT: survived bits=64 with 8-byte key
exit=0                                     # no key read at all for invalid bits
$ ./guard_rij_fixed safe
safe mode: bits=64 accepted silently, ctx.Nr=0 (0 = unusable schedule, no error surfaced)
exit=0                                     # encrypt refuses to run (returns early)
```

## Correctness cross-check

`rijkat`: FIPS-197 C.1/C.2/C.3 AES-128/192/256 vectors all PASS, Nr ∈ {10,12,14};
in-guest LibreSSL openssl enc -aes-{128,192,256}-ecb outputs byte-identical (run.log).
Still PASS after the fix (fixval.log).

See `guard.log` (baseline), `fixval.log` (patched), `run.log` (KAT + cross-match).
