# DF-3258 VERDICT — uninitialized dr[8..15] disclosure via /proc/<pid>/dbregs + PT_GETDBREGS

## Status: REPRODUCED (leak), unprivileged, 3/3 runs, fix validated

## Root cause (line-accurate)

1. `sys/cpu/x86_64/include/reg.h:89-96` — `struct dbreg { unsigned long dr[16]; }`
   with indices 8-15 documented "reserved".
2. `sys/platform/pc64/x86_64/machdep.c:3104-3129` — `fill_dbregs()` assigns
   `dr[0..3]` (pcb_dr0-3), `dr[4]=0`, `dr[5]=0`, `dr[6]` (pcb_dr6),
   `dr[7]` (pcb_dr7). `dr[8..15]` are never written. (Same in the vkernel
   twin, `sys/platform/vkernel64/x86_64/cpu_regs.c:782`.)
3. `sys/vfs/procfs/procfs_dbregs.c:57` — `struct dbreg r;` lives on the
   kernel stack uninitialized; `procfs_dbregs.c:65-67` fills it via
   `procfs_read_dbregs` → `fill_dbregs`, then `uiomove_frombuf(&r, sizeof(r), uio)`
   copies all 128 bytes to userspace. The read path requires only that the
   reader may inspect the target (`CHECKIO` + `p_trespass`, `procfs_dbregs.c:62`),
   which always holds for one's own process — the leaked stack is the
   *reader's own thread's* kernel stack (the uio is serviced synchronously
   in the reader's syscall context).
4. `sys/kern/sys_process.c:546-555` — `PT_GETDBREGS` constructs a
   `sizeof(struct dbreg)`-sized kernel uio over the caller's buffer and calls
   the same `procfs_dodbregs` → identical leak through ptrace.

Contrast: `fill_regs` (`machdep.c:3009-3017`) bcopy's the entire `struct reg`
from the trapframe, and `fill_fpregs` bcopy's the full fpu save area —
`fill_dbregs` is the only partial initializer of the three.

## Observation (stock kernel, run as uid=1001 maxx)

See `run.log` / `run.2.log` / `leak_sample.txt`:

- procfs path: 37/64, 33/64, 34/64 non-zero bytes in dr[8..15].
- ptrace path: 40/64, 47/64 non-zero.
- Stable kernel pointers across runs: `fffff800905f7980` (kernel heap),
  `fffff8011830f7f8`, `fffff800899794a8` (kernel stack region),
  `ffffffff8064ced0`, `ffffffff80680348` (kernel text/data).
- dr[0..7] always zero (target has no debug registers set) — the tail is
  pure uninitialized stack, not register state.

Guest: `DragonFly dfbsd 6.5-DEVELOPMENT #0 ... X86_64_GENERIC`,
cc 8.3, 6 vCPU. `/tmp` is tmpfs. Runs: 2026-09-09.

## Exploit chain / impact ceiling

Not a corruption primitive — an information disclosure. Ceiling: 64 bytes
of arbitrary-depth kernel stack residue per read, at will, from any
unprivileged process; direct kernel pointers defeat address-space
randomization of kernel text/heap/stack (relevant on hardened systems;
this guest runs without KASLR, where the value is reconnaissance of live
kernel data structures — e.g. pcb/proc pointers usable to defeat
independence assumptions of follow-up bugs).

## Fix validation

`fix.diff`: `bzero(dbregs, sizeof(*dbregs));` at the top of `fill_dbregs`
(single chokepoint for procfs + PT_GETDBREGS). Applied inside the guest's
/usr/src, `make -j6 nativekernel KERNCONF=X86_64_GENERIC`, reboot.

- Baseline (stock kernel): dr[8..15] = 33-47 non-zero bytes / 64 (3 runs).
- Patched kernel: dr[8..15] = 0 non-zero bytes / 64 on both paths
  (see run.patched.log); dr[0..7] unchanged (all zeros here);
  `PT_SETDBREGS` path unaffected (set_dbregs ignores dr[8..15]).
- No regressions observed: dbregs write of 128 bytes to a stopped same-uid
  target still works; fifo/procfs smoke clean after reboot.

## Files

- dbregs_leak.c — trigger (procfs + ptrace modes)
- run.log, run.2.log — vulnerable-kernel runs (3 leak samples)
- run.patched.log — post-fix run
- kbuild-tail.log — kernel build completion proof
- leak_sample.txt — extracted dr[8..15] samples
- fix.diff — the validated fix
- env.txt — guest environment

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