# DF-3564 — VERDICT

**Status: reproduced** (retention condition demonstrated in a same-source
userland build of the exact audited file; impact ceiling: hardening — no
standalone disclosure path exists in-tree).

## Narrative

Pass-2 audit of `sys/crypto/siphash/siphash.c` (235 LOC, KAME/FreeBSD-lineage
SipHash implementation by Andre Oppermann). The transform itself was verified
correct first (see below), then hygiene was reviewed against the file's own
standard:

- `SipHash_End()` wipes the entire context with `bzero(ctx, sizeof(*ctx))`
  (siphash.c:188) — key-influenced state does not linger in the context.
- `SipHash_SetKey()` (siphash.c:69-85) copies the caller's 16 raw key bytes
  into stack local `uint64_t k[2]` (line 71), uses them at lines 79-82, and
  returns without wiping. The verbatim key — stronger material than anything
  derivable from the context state alone — stays on the dead frame.

Demonstration (`sip_keyscan.c`): call `SipHash24_Init` + `SipHash_SetKey`
from a `noinline` wrapper, return, then scan the caller's downward stack with
an inline (call-free) byte comparison for the 16-byte key pattern.

Result (guest, stock kernel sources compiled as userland objects, -O0):

```
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
```

Methodological note: the first PoC attempt scanned with `memcmp(3)` and found
nothing — the libc call's own frame landed exactly on the dead `SetKey` frame
and overwrote it during comparison. The inline-loop version is the valid
measurement. In the kernel the analogous overwrite risk means the residue is
*not guaranteed* to survive arbitrary subsequent traffic processing, but it
is present at the moment of return and survives the common
SetKey→Update→End call pattern (Update's deeper frames sit below SetKey's
frame), so exposure windows are real, frequent, and cheap to eliminate.

## Why Low and not higher

No in-tree path exposes kernel stack contents to unprivileged readers. The
finding is the standard key-residue hardening class: it raises the blast
radius of any future unrelated info-leak primitive on the wg handler thread
stack. Consumers hash with long-term secrets (wg_noise l_hash_key,
wg_cookie MAC keys) at attacker-driven rates.

## Correctness work that preceded this finding (negative results with weight)

Same-source KAT suite `sip_kat.c`, all PASS (2111 checks, 0 failures):

- All **64 official reference vectors** (key 000102..0f, in = first i bytes
  of 00..ff, SipHash-2-4) — identical to the in-tree
  `sys/crypto/siphash/siphash_test.c` table — match.
- Kernel vs an **independently written from-the-paper reference** for
  SipHash-2-4 and SipHash-1-3 (the wg_cookie variant): 1024 cross-checks
  (3 keys × 256 lengths × 2 round-variants) match. (An earlier revision of
  my scratch reference had the partial-word byte placement reversed; the
  veorq-reference semantics — byte j at shift 8*j — were re-derived and the
  kernel confirmed correct. The failure was in my probe, not the kernel.)
- **Chunked-update equivalence** (256 random 2-way and 3-way splits)
  exercises SipBuf partial-word buffering incl. buflen>0 crossing: matches.
- `SipHash_Final` emits the LE encoding of `SipHash_End`: verified.

Memory-safety review: `SipBuf` bounds (`MIN(len, 8 - buflen)`), the
`final` path (only writes `buf.b8[7]`, never dereferences `src`), the
aligned/unaligned word loops, and `bytes` (uint64) overflow were all traced
and are sound. `SipHash_Update(ctx, p, 0)` trips the KASSERT on INVARIANTS
kernels (harmless on production builds) — no in-tree caller passes 0; noted,
not filed.

## Fix validation

`fix.diff` adds `explicit_bzero(k, sizeof(k));` after last use. Validated on
a patched copy of the file (never applied to `sys/`):

- baseline: key found at stack offset -112 (reproduced=1)
- patched: NOT FOUND (exit 1) — residue eliminated
- patched KAT suite: 2111/2111 PASS — fix is behavior-preserving

See `fix_run.log`.

## Kernel references

- sys/crypto/siphash/siphash.c:69-85 (SipHash_SetKey, unwiped k[2])
- sys/crypto/siphash/siphash.c:188 (SipHash_End's bzero — the contrast)
- sys/net/wg/wg_noise.c:240,457,498 (secret-key consumers)
- sys/net/wg/wg_cookie.c:136 (network-reachable consumer)

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