# DF-0034 — VERDICT

**Verdict: REPRODUCED (info leak). Fix VALIDATED.**

`st_padding1` (`__uint16_t`, `sys/sys/stat.h:105`) is never written by
`vn_stat()` (`sys/kern/vfs_vnops.c:832-977`). `vn_stat` deliberately zeros
the other two spare fields at `:852-853` (`st_lspare`, `st_qspare2`) but
omits `st_padding1`. Every stat-family syscall handler
(`sys_fstat`/`sys_stat`/`sys_lstat`/`sys_fstatat`/`sys_fhstat`) declares
`struct stat st;` on the kernel stack **unzeroed** and `copyout()`s
`sizeof(struct stat)`, so the 2 uninitialized bytes at
`offsetof(struct stat, st_padding1)` (offset 18 on amd64, between `st_mode`
and `st_uid`) reach userspace containing stale kernel-stack residue.

## Mechanism (trigger → primitive → effect)

1. **Trigger** — any unprivileged `fstat()`/`stat()`/`lstat()`/`fstatat()`/
   `fhstat()` on any vnode. No setup, no privilege, default GENERIC kernel.
2. **Primitive** — 2-byte info leak of uninitialized kernel stack at a fixed
   offset in the returned `struct stat`. The handler's stack-local
   `struct stat st;` is not zeroed (`sys/kern/kern_descrip.c:1574`,
   `sys/kern/vfs_syscalls.c:3071,3093,3115`), `vn_stat` fills every field
   *except* `st_padding1`, and `copyout(&st, ub, sizeof(st))` ships the
   residue.
3. **Effect** — per-call leak of 2 bytes of kernel stack (pointer fragments,
   prior-syscall residue). Samplable in a tight loop; Low-grade info-scrape /
   KASLR-assist. **No write primitive, no escalation.**

## Confirmed by

- **Source trace** — `sys/kern/vfs_vnops.c:852-853` zeros `st_lspare` and
  `st_qspare2` but never assigns `st_padding1`; `sys/sys/stat.h:105` declares
  it `__uint16_t`; the five handlers at `kern_descrip.c:1574`,
  `vfs_syscalls.c:3071/3093/3115` all stack-allocate `struct stat st;`
  unzeroed and `copyout sizeof`.
- **Runtime (unpatched `#0` kernel)** — `./leak_sharp` over 20000 `fstat()`
  calls showed **7402–17844 samples** with non-zero `st_padding1[0]` across
  three runs, with **run-to-run varying dominant bytes**:
  - Run 1: `0xfe (10350)`, `0x69 (7494)`
  - Run 2: `0x19 (9364)`, `0x68 (1733)`, `0x1c (1)`
  - Run 3: `0x50 (5437)`, `0x42 (1)`
  The distribution shifts each run and occasional wild values appear — the
  signature of genuine uninitialized stack data, not cosmetic output.

## Exploit chain

Not applicable — this is a **read-only info leak** (Phase 6 valid hard
blocker: primitive is genuinely read-only, no write/corruption). Impact
ceiling is 2 bytes of kernel-stack residue per stat call, samplable in a
loop for low-grade KASLR-assist / info-scrape. No `uid=0` derivable.

## Fix

`fix.diff` adds `sb->st_padding1 = 0;` in `vn_stat()` next to the existing
`st_lspare`/`st_qspare2` zeroing (`sys/kern/vfs_vnops.c:852`). This is the
root-cause fix — `vn_stat` is the single function that fills the `struct stat`
for every vnode stat path, so one line closes all five syscall handlers.
**Matches the finding markdown's `## Recommended fix` proposal.**

(Defense-in-depth alternative: `bzero(&st, sizeof(st))` in each handler
before the `vn_stat`/`fo_stat` call. The minimal single-point fix in
`vn_stat` is sufficient and is what was validated.)

## Fix validation (Phase 8)

| Kernel | `kern.version` | leak_stpad result | leak_sharp result |
|--------|----------------|-------------------|-------------------|
| unpatched baseline | `6.5-DEVELOPMENT #0` (Jul 2 06:02) | 10218/20000 non-zero | 7402–17844/20000, varying bytes |
| **single-fix** | `6.5-DEVELOPMENT #1` (Jul 12 23:02) | **0/20000** (`00 00` every sample) | **0/20000** |

Built with `make -j6 nativekernel KERNCONF=X86_64_GENERIC` from `/usr/src`
with only `fix.diff` applied; installed `kernel.stripped` → `/boot/kernel/kernel`
+ `kernel.debug`; rebooted; `kern.version` bumped `#0 → #1`. Re-ran both PoCs
3× — **zero non-zero `st_padding1` bytes on every run**. Fix is deterministic,
closes the leak completely.

## PoC changes

- `leak_stpad.c` — unchanged (the original reviewer-written minimal PoC; it
  builds and runs as-is on DragonFly 6.5-DEVELOPMENT, gcc 8.3).
- `leak_sharp.c` — **added**: sharper variant that prints the first 16
  non-zero samples + a byte-value histogram, making the run-to-run variance
  of the leaked residue visible (proof it is real stack data, not cosmetic).
- `build.sh` / `run.sh` — **added**: exact, runnable repro scripts.
