# DF-0654 — `ccmp_setkey` omits AES key load for software-decrypt-only CCMP keys

## Verdict: BUG CONFIRMED REAL by source trace — not runnable on guest (no WiFi hardware); impact ceiling = deterministic kernel panic (DoS)

## Bug summary (source-confirmed)

`ccmp_setkey()` (`sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:130`) loads
the AES key schedule **only** when `IEEE80211_KEY_SWENCRYPT` (0x0010) is set:

```c
129  static int
130  ccmp_setkey(struct ieee80211_key *k)
...
134      if (k->wk_keylen != (128/NBBY)) { ... return 0; }
140      if (k->wk_flags & IEEE80211_KEY_SWENCRYPT)            // <-- gates ONLY on SWENCRYPT
141          rijndael_set_key(&ctx->cc_aes, k->wk_key, k->wk_keylen*NBBY);
142      return 1;
143  }
```

But `ccmp_decap()` calls `ccmp_decrypt()` when `IEEE80211_KEY_SWDECRYPT` (0x0020)
is set (`ieee80211_crypto_ccmp.c:266-268`), and `ccmp_decrypt()` →
`ccmp_init_blocks()` → `rijndael_encrypt()` uses the **same** `ctx->cc_aes`.

`ccmp_attach()` (`:97-104`) allocates the context with `M_ZERO`, so if a key is
configured **SWDECRYPT without SWENCRYPT**, `ccmp_setkey` skips
`rijndael_set_key` and `cc_aes` stays zeroed: `Nr == 0`, `ek[]`/`dk[]` all zero.

In `rijndaelEncrypt()` (`sys/crypto/rijndael/rijndael-alg-fst.c:862`), with `Nr=0`:

```c
946   rk += Nr << 2;        // rk unchanged
951   r = Nr >> 1;          // r = 0
952   for (;;) {
953       ... read rk[4..7]; rk += 8;
979       if (--r == 0) break;   // --r => -1, NEVER == 0 -> loop never breaks
...
```

`--r` makes `r == -1` (never 0), so the loop **never terminates**; `rk += 8` each
iteration walks `rk` past `ek[60]`, `dk[60]`, out of the `ccmp_ctx` and into
adjacent kernel heap. The `Te0[(s0>>24)]` lookups index on out-of-bounds memory
and `rk[]` reads eventually hit an unmapped page → **fatal page fault**.

This fault occurs inside `ccmp_init_blocks()` (CBC-MAC block construction) **before**
any MIC check, so an attacker need only deliver a single privacy-bit-set 802.11
data frame (>=32 B) to a BSS/STA whose driver configures CCMP SWDECRYPT-only.

## Reachability of the SWDECRYPT-without-SWENCRYPT config

`ieee80211_crypto.c::ieee80211_crypto_newkey()`:
- `:320-325` — if the hardware lacks the cipher, it sets **both** SWCRYPT bits
  (`flags |= IEEE80211_KEY_SWCRYPT`), so the AES key IS loaded — NOT the vulnerable case.
- `:386-410` — the **driver-flag override** path: after `dev_key_alloc()`, if the
  driver changed `wk_flags` (e.g. a driver doing HW-encrypt / SW-decrypt
  asymmetric offload), the cipher context is re-attached with the driver's flags.
  A driver that clears SWENCRYPT while keeping SWDECRYPT yields the vulnerable
  state where `ccmp_setkey` skips the key load but `ccmp_decap` still decrypts.

So the buggy state is reachable via the framework's driver-override path; whether
it manifests depends on a driver setting SWDECRYPT-without-SWENCRYPT (uncommon but
within the framework's design — asymmetric crypto offload).

## Reproduction

**Not runnable on this guest**: there is no WiFi hardware / ieee80211 VAP, so the
CCMP cipher is never attached and `ccmp_setkey`/`ccmp_decap` cannot be invoked.
This is a source/logic finding verified by code trace + the mathematically-certain
`rijndaelEncrypt` `Nr=0` runaway-loop argument. (The `#else /* !FULL_UNROLL */`
branch at `rijndael-alg-fst.c:947` is the one compiled by the DragonFly build.)

## Fix

Gate the AES key load on `IEEE80211_KEY_SWCRYPT` (= `SWENCRYPT|SWDECRYPT`) so the
key schedule is loaded whenever EITHER software path is used (`fix.diff`):

```c
-   if (k->wk_flags & IEEE80211_KEY_SWENCRYPT)
+   if (k->wk_flags & IEEE80211_KEY_SWCRYPT)
        rijndael_set_key(&ctx->cc_aes, k->wk_key, k->wk_keylen*NBBY);
```

Matches the finding proposal (gate on SWCRYPT). `IEEE80211_KEY_SWCRYPT` is defined
at `sys/netproto/802_11/ieee80211_crypto.h:107-108`.

## Kernel references

- `sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:130-143` — `ccmp_setkey`
- `sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:140` — gates on SWENCRYPT only
- `sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:266-268` — `ccmp_decap` decrypts on SWDECRYPT
- `sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:104` — `ccmp_attach` M_ZERO (Nr=0)
- `sys/crypto/rijndael/rijndael-alg-fst.c:946-981` — `rijndaelEncrypt` Nr=0 runaway loop
- `sys/netproto/802_11/wlan/ieee80211_crypto.c:320-325,386-410` — flag set / driver-override paths
- `sys/netproto/802_11/ieee80211_crypto.h:82,83,107` — SWENCRYPT/SWDECRYPT/SWCRYPT
