# DF-2367 VERDICT — data-dependent T-table lookups leak key material via cache timing

**Status: REPRODUCED (side-channel mechanism, byte-exact)** — impact: leak —
confidence: likely
**Rig:** DragonFly 6.5-DEVELOPMENT x86_64 stock X86_64_GENERIC (INVARIANTS),
QEMU/KVM, kernel-compiled sysctl harness shared with DF-2368.

## What was demonstrated (through the REAL kernel code)

1. **The lookup indices are secret-dependent.** rijndaelEncrypt performs 16
   table lookups per round (`Te0[(s0>>24)] ^ Te1[(s1>>16)&0xff] ^ ...`,
   rijndael-alg-fst.c:953-1007 round loop, 1013-1040 final round) where the
   state words are `GETU32(pt) ^ rk[i]` — in round 1 literally
   `index = key_byte XOR plaintext_byte`. The instrumented twin (validated
   byte-exact against the real `rijndael_encrypt`: 600 + 300 vectors, 0
   mismatches, plus `ct(real==twin)` asserted per trace) dumping the exact
   index sequence through the REAL `rijndaelKeySetupEnc` schedule showed:

       K  (key=000102...0f, pt=00112233...eff): round-1 indices 0050a0f04090e03080d02070c01060b0
       K' (key byte0  00->80):                 round-1 indices 8050a0f04090e03080d02070c01060b0
       K''(key byte15 0f->ff):                 round-1 indices 0050a0004090e03080d02070c01060b0

   One flipped key byte -> EXACTLY the corresponding index flips (position 0
   resp. 3); 136/160 (K vs K-prime) index positions diverge after avalanche;
   total bitwise distance 534/1280. (leak_sample.txt, idx_analysis.txt)

2. **The identities hold statistically.** Across the 24x8 key/plaintext matrix,
   40/40 cleanly-parsed samples satisfy ALL 16 round-1
   `index[j] == key[b] ^ pt[b]` identities (idx_analysis2.txt). The remaining
   dmesg lines were mangled by console interleaving, not failures.

3. **The channel granularity is cache-line.** Tables live at fixed kernel
   addresses (ttables.txt: Te0 0xffffffff80d897e0, Te1 ..930, Te2 ..8fe0,
   Te3 ..8be0, each 0x400 = 1024 bytes = 16 cache lines of 64 B; a lookup at
   index i touches line i>>4). K vs K-prime touch Te0 lines 0 vs 8 in round 1
   — perfectly distinguishable to a line-granularity observer. Every AES-128
   block performs 160 such lookups (AES-192: 208, AES-256: 240).

4. **Aggregated timing does NOT separate keys on this rig (honest negative).**
   bench: 59-69 ns/op for both keys, overlapping across 3 alternating runs —
   expected: with resident tables the leak is per-access line occupancy, not
   total execution time. Consequently a Bernstein-style remote/aggregate
   timing attack was not observable here, and an end-to-end
   Prime+Probe/Flush+Reload key recovery was NOT attempted: QEMU/KVM gives no
   cache-occupancy measurement fidelity for a cross-mode (user-observes-kernel)
   attack. The reproduced fact is the secret-dependent memory-access pattern
   itself — the observable that cache attacks measure.

## Reachability (who runs this code with attacker-visible traffic)

- **wlan_ccmp** — software CCMP for 802.11 (ieee80211_crypto_ccmp.c:141
  set_key, :399-417 and :559-563 several `rijndael_encrypt` calls PER FRAME,
  no AES-NI bypass anywhere in the file). Any DF system doing WPA2-CCMP in
  software exposes the channel to a co-resident local process; frame data is
  attacker-influenceable (inject/observe traffic).
- **cryptoapi AES-CBC/XTS -> dm-crypt** (cryptoapi.c:314/405-406, sole consumer
  dm_target_crypt.c) — every block IO to an encrypted volume walks these
  tables with the volume key; setup is privileged but the crypto runs on IO
  triggered by unprivileged users of the mapped volume.
- opencrypto/IPsec do NOT use this implementation (separate aesni/xform path),
  correcting the filing wording slightly: fast_ipsec sessions are not affected
  by THIS file.

## Severity / privesc assessment

Medium as filed (AV:L/AC:H, confidentiality-only) — confirmed. Information
leak of key material to a co-resident local observer; no write primitive, no
direct kernel-memory disclosure, no privesc chain — **privesc: closed** for
this finding (crypto-core class: local key confidentiality issue).

## Why no fix.diff in this pack

The remedy is to stop using table-driven AES in these paths: route CCMP and
cryptoapi software AES to the in-tree AES-NI implementation (sys/crypto/aesni)
with a constant-time bitsliced fallback, or adopt a constant-time C
implementation. That is an algorithm/API swap across three subsystems — not a
minimal patch whose A/B this rig could validate (correctness vectors alone
would not demonstrate constant-time behavior on QEMU). Recommendation recorded
in verdict.json; no code change was applied to sys/.

## Reproducibility

Same rig as DF-2368 (`sh build.sh`, then `sh run_idx.sh`; outputs in dmesg).
Deterministic: identical key+pt yield identical traces across boots (twin
byte-exact on every run), and the 16 embedded KATs produce identical
ciphertexts on stock and fixed kernels (DF-2368 A/B), demonstrating run-to-run
stability of the primitive.

## 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.

(No fix.diff for this item: documented recommendation only - see Fix above.)
