# DF-3564 — SipHash_SetKey retains verbatim 128-bit key on the stack

**Target:** `sys/crypto/siphash/siphash.c` (pass 2 GLM 5.3 re-audit)
**Class:** key-material hygiene / CWE-459 (Incomplete Cleanup)
**Severity:** Low (hardening; no standalone leak path)

## What

`SipHash_SetKey()` (sys/crypto/siphash/siphash.c:69-85) decodes the caller's
16-byte key into a **stack local** `uint64_t k[2]` (line 71), XORs it into the
context state, and returns **without zeroizing `k`**. The same file's own
hygiene standard — `SipHash_End()` at line 188 does `bzero(ctx, sizeof(*ctx))`
— makes the omission stand out: the *context* is wiped, but the *raw key copy*
on the dead frame is not.

## Reachability

In-tree consumers (all WireGuard, all network-reachable with attacker-driven
call rates on the handshake path):

- `sys/net/wg/wg_noise.c:240` `siphash24()` → `SipHashX(ctx, 2, 4, key, ...)`
  used at `wg_noise.c:457` and `:498` to index the handshake/remote hash
  tables with the **long-term secret** `l_hash_key` (random, per-locality).
- `sys/net/wg/wg_cookie.c:136` `siphash13()` → `SipHashX(ctx, 1, 3, key, ...)`
  over received handshake message bytes (MAC1 path).

Every received UDP handshake packet to a wg interface causes `k[2]` to hold
16 bytes of a long-term secret on the handler thread's kernel stack. The
bytes persist until that stack region is reused. No in-tree primitive reads
kernel stacks back to userspace today — hence Low / hardening, not a leak
finding — but it is exactly the kind of residue a second, unrelated
info-leak bug would harvest.

## Reproduce (same-source userland-object pattern)

```
# guest, in this directory (sources + shim included)
cc -O0 -g -I shim -c /usr/src/sys/crypto/siphash/siphash.c -o siphash.o
cc -O0 -g -I shim sip_keyscan.c siphash.o -o sip_keyscan
./sip_keyscan
```

Expected (baseline, unpatched `sys/crypto/siphash/siphash.c`):

```
RETAINED: verbatim 16-byte key found on the dead stack, offset -112 below caller frame
DF-3564 CONFIRMED: raw key bytes persist on the stack after SipHash_SetKey returned
```

The scan uses an inline byte-compare loop (no calls), so the comparison
itself cannot clobber the dead frame being inspected — the first version of
this PoC used `memcmp(3)` and self-clobbered the evidence.

## Fix

`fix.diff` (one line): `explicit_bzero(k, sizeof(k));` after the last use of
`k`. Validated live: with the patched copy the scan reports NOT FOUND
(exit 1) and the full SipHash KAT suite still passes 2111/2111 (see
`fix_run.log`), so the fix is behavior-preserving.

## Full-suite context

`run.log` in this pack is the decisive run of the whole four-file batch:
sha1 1859/0, siphash 2111/0, des 8916/0, blake2s 6878/8 checks — all PASS —
plus this retention demonstration. `env.txt` records guest uname, compiler
(LibreSSL 3.6.1 cross-match) and byte-identical source hashes between the
guest `/usr/src` and the audited tree.
