# DF-1056 — Verification Verdict

## Verdict: REPRODUCED → FIXED (info leak, ~311-404 bytes of kernel stack per call)

**Severity:** Medium (info leak, local same-uid prerequisite, confidentiality-only).

---

## Summary

The finding is **real and confirmed** on the default `X86_64_GENERIC` kernel
via the **sibling defect** the finding explicitly notes at
`sys/platform/pc64/x86_64/machdep.c:3078` (`fill_fpregs()`). The finding's
primary citation targets the vkernel64 platform
(`sys/platform/vkernel64/x86_64/cpu_regs.c:755`), but the booted audit guest
is a normal pc64 kernel — which has the **identical** `fill_fpregs()` /
`fill_fpregs_xmm()` code with the same partial-init bug. Both call paths
(ptrace `PT_GETFPREGS` and procfs `/proc/<pid>/fpregs`) leak uninitialized
kernel-stack bytes to userspace. The **fix (bzero before fill) closes both
paths and was validated** by building a single-fix kernel and confirming the
leak drops to 0.

## Mechanism (confirmed, each hop cited)

1. **Trigger.** Any local user calls `ptrace(PT_GETFPREGS, victim_pid, &fp, 0)`
   on a same-uid process, or reads `/proc/<victim_pid>/fpregs`. Both reach
   `procfs_dofpregs()` (`sys/vfs/procfs/procfs_fpregs.c:48`) which declares
   `struct fpreg r;` on the kernel stack **with no initializer**
   (`procfs_fpregs.c:54`).

2. **Partial fill.** `procfs_read_fpregs(lp, &r)` → `fill_fpregs(lp, &r)`
   (`sys/platform/pc64/x86_64/machdep.c:3078`). On the `cpu_fxsr` path (the
   only path that runs on x86-64), it calls `fill_fpregs_xmm()` which writes
   only:
   - `env87` fields `en_cw..en_fos` (28 bytes — `sys/cpu/x86_64/include/npx.h:50-59`)
   - `sv_ac[0..7]` (8 × `fpacc87` = 80 bytes — `npx.h:62-69`)
   - **Total = 108 bytes written.**

3. **Leak.** `sizeof(struct fpreg) = 512` (`sys/cpu/x86_64/include/reg.h:74-84`:
   `fpr_env[4]` + `fpr_acc[8][16]` + `fpr_xacc[16][16]` + `fpr_spare[12]`).
   Bytes `108..511` of the stack-allocated `r` are **never touched**. Then
   `uiomove_frombuf(&r, sizeof(r), uio)` (`procfs_fpregs.c:65`) copies all
   512 bytes — including the 404 uninitialized kernel-stack bytes — to the
   caller's userspace buffer. The ptrace path
   (`sys/kern/sys_process.c:515-524`) sets `iov.iov_len = sizeof(struct fpreg)`
   and routes through the same `procfs_dofpregs`.

## Reproduction evidence (unpatched `#0`)

ptrace path, 3 stress runs — byte-count varies (310/313/316), proving genuine
stack residue not a constant:

```
PT_GETFPREGS returned 512 bytes, 316 non-zero/non-poison in [108..511]
  fpregs[+128] = 0xfffff80117ff0518   ← canonical KVA kernel pointer
  fpregs[+136] = 0xfffff80116baae80   ← another KVA pointer
  fpregs[+168] = 0xffffffff80c18ac4   ← fixed kernel-text return address
  ...
```

procfs path: `268` non-zero bytes in `[108..511]`, raw hex showing
`00 f8 ff ff` (high halves of KVA pointers) and `ff ff ff ff 80 ...`
(kernel-text addresses).

The leaked 64-bit words are DragonFly kernel-virtual addresses
(`0xfffff801xxxxxxxx` = KVA, `0xffffffff80xxxxxx` = kernel text) and
kernel-text return addresses — exactly the KASLR-bypass / stack-pivot useful
data described in the finding.

## Fix (authored, built, validated)

**`fix.diff`** — single targeted change in
`sys/platform/pc64/x86_64/machdep.c:fill_fpregs()`: add
`bzero(fpregs, sizeof(*fpregs))` before the `cpu_fxsr` dispatch. This closes
**both** ptrace and procfs paths at the single chokepoint, regardless of
caller.

```diff
+	bzero(fpregs, sizeof(*fpregs));
 	if (cpu_fxsr) {
 		fill_fpregs_xmm(...);
```

**Supersedes** the finding markdown's proposal (which targeted the vkernel64
`cpu_regs.c` file): the fix here targets the **pc64 `machdep.c`** file because
that is the path exercised on the booted `X86_64_GENERIC` kernel. The same
one-line `bzero()` should also be applied to
`sys/platform/vkernel64/x86_64/cpu_regs.c:755` (the finding's primary
citation) for parity — that file is identical in structure.

**Build:** `make -j6 nativekernel KERNCONF=X86_64_GENERIC` on the `with-src`
guest; copied `kernel.stripped` (15.7 MB, NOT the 119 MB debug kernel which
the DragonFly loader cannot load) to `/boot/kernel/kernel`; rebooted into
`6.5-DEVELOPMENT #1`.

## Fix validation (patched `#1`)

| Path                 | Before (unpatched `#0`) | After (patched `#1`) |
|----------------------|-------------------------|----------------------|
| ptrace PT_GETFPREGS  | 310-316 leaked bytes    | **0** leaked bytes   |
| procfs /proc/.../fpregs | 268 leaked bytes     | **0** leaked bytes   |

All 8 inspected 64-bit words at offsets `128..184` are `0x0000000000000000`
on the patched kernel (were kernel pointers on unpatched). Verified across
3 ptrace + 3 procfs runs — deterministic.

**Kernel build identifiers:**
- Unpatched baseline: `6.5-DEVELOPMENT #0`, sha256 `5dc83dac...`
- Patched single-fix: `6.5-DEVELOPMENT #1` (Jul 14 17:58), sha256 `77022b65...`

## No escalation chain

This is a **pure info leak** (read-only primitive, no memory corruption).
There is no escalation chain to develop — the realistic impact ceiling is
~404 bytes of kernel-stack disclosure per call (useful for KASLR bypass and
stack-pivot setup for a *subsequent* memory-corruption exploit, but not a
privesc on its own). CVSS 3.1 `AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N` (Medium).

## PoC changes

- `leak_fpregs.c` — unchanged from the finding's original (compiles and runs
  as-is on the guest).
- `procfs_c.c` — **added** by the verifier: a procfs-path reader that opens
  `/proc/<child>/fpregs` and counts non-zero bytes in `[108..511]`, to
  independently confirm the second call path and provide a clean
  RLE-artifact-free byte count.

## References (verified during this run)

- `sys/platform/pc64/x86_64/machdep.c:3034-3053` — `fill_fpregs_xmm` (partial init, 108/512 bytes)
- `sys/platform/pc64/x86_64/machdep.c:3078-3089` — `fill_fpregs` (no zeroing; the patched function)
- `sys/platform/pc64/x86_64/procfs_machdep.c:108-112` — `procfs_read_fpregs` → `fill_fpregs`
- `sys/vfs/procfs/procfs_fpregs.c:54,63-65` — stack `struct fpreg r;` + `uiomove_frombuf` all 512 bytes
- `sys/kern/sys_process.c:515-524` — `PT_GETFPREGS` → `procfs_dofpregs`
- `sys/cpu/x86_64/include/reg.h:74-84` — `struct fpreg` 512-byte layout
- `sys/cpu/x86_64/include/npx.h:50-69` — `env87` (28 B) + `fpacc87` (10 B)
