# DF-3563 — chacha_encrypt_bytes leaves partial-block plaintext in `tmp[64]` on the kernel stack

File: `sys/crypto/chacha20/chacha.c` (pass 2, GLM 5.3)
Severity: Info (hardening / defense-in-depth) — CWE-226

## Root cause

`chacha_encrypt_bytes()` (sys/crypto/chacha20/chacha.c:110-261) handles a
final partial block (`bytes < 64`, chacha.c:139-148) by copying the
message tail into a 64-byte stack buffer `tmp` (chacha.c:116), encrypting
into `tmp`, copying `bytes` out to `ctarget` (chacha.c:243-246), and
returning **without clearing `tmp`**. Up to 63 bytes of the caller's
buffer contents — plaintext in the caller's frame — remain in the current
kernel stack frame after return, available to any later (separate,
independent) kernel-stack-disclosure primitive on the same CPU/thread.

This matches upstream (Bernstein chacha-merged 20080118 / OpenBSD
chacha.c r1.1 lineage) — it is an inherited hardening gap, not a DFly
regression.

## Reachability (live-confirmed consumers)

- `sys/crypto/chachapoly.c:170/174` (`_chacha20poly1305_final`) calls
  `chacha_encrypt_bytes(..., in_len % 64)` for **every** wg message whose
  data length is not a multiple of 64 — i.e. nearly all real traffic
  (`sys/net/wg/wg_noise.c` transport path; `wg_cookie.c` cookie path).
  On the encrypt direction, `tmp` retains the outbound packet's plaintext
  tail; on decrypt, hostile-chosen ciphertext (not secret).
- `sys/kern/subr_csprng.c:155` (KEYSTREAM_ONLY embed build): the
  `KEYSTREAM_ONLY` configuration never copies message data into `tmp`
  (chacha.c:143-145) — only keystream (the RNG output itself) passes
  through; the RNG tail buffer retains delivered-by-construction output.
- rmd160.c / poly1305.c equivalents wipe properly: `RMD160Final` memsets
  the whole context (rmd160.c:156, proven live); `poly1305_finish` zeroes
  the key-bearing h/r/pad state (poly1305.c:287-300, proven live).

No exploit on its own: requires a separate stack-content disclosure bug
to matter. Not memory-unsafe, not reachable as UAF/OOB; the primitive
itself was KAT-verified clean (917 checks, see
`findings/poc/neg-evidence-crypto-pass2/`).

## Fix

`explicit_bzero(tmp, sizeof(tmp))` before the return at chacha.c:253
(see fix.diff).

## Verification

Info hardening finding → Phase V PoC skipped per audit contract (no
runtime manifestation to reproduce; correctness of the primitive
verified in the same run via the KAT harness in
`neg-evidence-crypto-pass2/`). Fix validation not run (`fix_status:
not_tested`).
