# DF-3568 VERDICT

**Status: reproduced (same-source userland-object harness, in-guest) — impact: none (all in-tree callers validate).**
**Fix: validated at harness level (fixed).**

## What was audited

Pass-2 full audit of `sys/crypto/rijndael/rijndael-api.c` (57 LOC): the
makeKey-style glue consumed by wlan CCMP (remote packet path) and cryptoapi
(dm-crypt, hammer2). `rijndael-api-fst.c` (the other, unused API) went clean as
dead code in a prior batch; this file is live.

## Root cause

`rijndael_set_key(ctx, key, bits)` (rijndael-api.c:38-43) is `void`, returns no
error, and passes `bits` to `rijndaelKeySetupEnc/Dec` unvalidated. The only guard
is a KASSERT in the alg (rijndael-alg-fst.c:738) — compiled out on production
(non-INVARIANTS) kernels. With `bits ∉ {128,192,256}`:

1. **Key overread:** the rk[0..3] loads (alg:740-743) run *before* any keyBits
   branch, and rk[4..7] (alg:762-785) run whenever bits != 128/192 — so up to 32
   bytes of key material are read regardless of the actual key size.
2. **Nr = 0:** every keyBits branch is skipped → `return 0` (alg:814) → ctx->Nr = 0,
   silently.
3. **Wild round-key walk:** `rijndaelEncrypt` computes `r = Nr >> 1` (= 0) and its
   main loop breaks only `if (--r == 0)` (alg:951-979; decrypt identical at
   1132-1160) — r goes to -1 and the loop walks `rk += 8` forever, reading
   progressively further past the 60-word schedule until unmapped memory. In
   kernel context: panic-class DoS.

## Reachability (why Info)

Both in-tree consumers validate before calling:

- `ieee80211_crypto_ccmp.c:134-141` — ccmp_setkey rejects wk_keylen != 16 before
  `rijndael_set_key(&ctx->cc_aes, k->wk_key, k->wk_keylen*NBBY)`.
- `cryptoapi.c:308-320` — aes_cbc_setkey switch-validates 128/192/256; XTS halves
  validated (cryptoapi.c:376-386, 398-403) to 128/256 each.

Both consumers are privileged/config-driven (wpa_supplicant-driven setkey for the
first, dmsetup/hammer2 mount for the second), and neither can presently deliver an
invalid bits value. The hazard is the API contract for future callers: void return +
no validation + assert-only guard.

## Verification (guest: DragonFly 6.5-DEVELOPMENT #0, gcc 8.3 [DragonFly])

`-DNDEBUG` disables the userland `KASSERT=assert` (alg-fst.c:34-36) — exact
production-kernel semantics.

1. Guest source sha256 == audited tree (5727df5130a6ef37b93a299b598f9bb71241bed3f57b8686bdaf511457be8b2e).
2. **Baseline overread** (guard.log): 8-byte key at a page edge, bits=64 → SIGSEGV,
   `si_addr == key+8` (exit 42) — the first OOB byte load of the rk[2]=GETU32(key+8)
   word. Proves key is read past keyBits/8.
3. **Baseline wild walk** (guard.log): 32-byte safe buffer, bits=64, then
   `rijndael_encrypt` → SIGSEGV exit 139 — the Nr=0 round-key walk (no handler
   installed in safe mode, hence raw segfault). In-kernel equivalent: panic.
4. **Correctness** (run.log): FIPS-197 C.1/C.2/C.3 vectors PASS for 128/192/256 with
   Nr = 10/12/14; in-guest LibreSSL `openssl enc -aes-{128,192,256}-ecb` outputs
   byte-identical.
5. **Fix validation** (fixval.log): with fix.diff (reject invalid bits without
   reading key; encrypt/decrypt refuse Nr ∉ {10,12,14}) applied to a source copy:
   guard mode exit 0 (no key read at all), safe mode exit 0 (no wild walk —
   rijndael_encrypt returns early; note the harness's "produced garbage" line
   predates the fix semantics: with the fix ct is simply left untouched), rijkat
   still all-PASS.

## Exploit chain

None — no unprivileged trigger path; consumers validate. Hardening only.

## kernel_refs

- sys/crypto/rijndael/rijndael-api.c:38-43 (unvalidated bits, void return)
- sys/crypto/rijndael/rijndael-alg-fst.c:738 (KASSERT-only guard)
- sys/crypto/rijndael/rijndael-alg-fst.c:740-785 (unconditional key loads)
- sys/crypto/rijndael/rijndael-alg-fst.c:951-979, 1132-1160 (Nr=0 wild walk)
- sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:134-141 (validating caller)
- sys/crypto/cryptoapi/cryptoapi.c:308-320, 398-403 (validating caller)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
