# DF-5657 VERDICT — reproduced (kernel panic from unprivileged local user), fix validated

## What was tested

`sys/platform/pc64/x86_64/procfs_machdep.c` is the owner file; its
`procfs_write_dbregs()` (procfs_machdep.c:97-101) and the ptrace compat
plumbing (`kern_ptrace` PT_SETDBREGS → `procfs_dodbregs`,
sys/kern/sys_process.c:531-560) both funnel into
`set_dbregs()` in `sys/platform/pc64/x86_64/machdep.c:3133`.

## Root cause (path:line)

1. `set_dbregs()` (machdep.c:3166-3169) validates only the four R/W fields of
   dr7 (rejects the 0b10 I/O-breakpoint pattern) and
   machdep.c:3189-3213 bounds dr0–dr3 to user space *when enabled* (gated on
   `caps_priv_check(SYSCAP_RESTRICTEDROOT)`). Nothing checks the
   architecturally-reserved bits. `pcb->pcb_dr7 = dbregs->dr[7];`
   (machdep.c:3220) stores the raw 64-bit user value and
   `pcb->pcb_flags |= PCB_DBREGS` (machdep.c:3222) arms the reload path.
2. On the next context switch into the tracee, `cpu_heavy_restore`
   (sys/platform/pc64/x86_64/swtch.s:597-617) reloads the debug registers:
   `%dr7 ← (current_dr7 & 0x0000fc00) | (pcb_dr7 & ~0x0000fc00)`. Only bits
   10–15 (incl. the GD bit) are masked; **bits 63:32 pass through raw** into
   `movq %rax,%dr7`.
3. DR7 bits 63:32 are reserved: Intel raises #GP(0) on `MOV DR7` with them
   set, and under KVM the host `kvm_set_dr()` (Linux arch/x86/kvm/x86.c)
   unconditionally returns 1 (→ injected #GP) `if (val & 0xffffffff00000000ULL)`
   for DR7 — independent of host vendor.
4. The #GP lands in ring 0 inside the context-switch path. The kernel-mode
   T_PROTFLT handler only redirects faults whose RIP is `doreti_iret`
   (trap.c:675-681, matching exception.S IDTVEC(prot)'s special case at
   exception.S:351-371). A fault at swtch.s is not at doreti_iret, has no
   pcb_onfault, so it falls through to `trap_fatal(frame, 0)` (trap.c:770).

## Observation (decisive)

Stock guest kernel, unprivileged uid 1001:

```
[+] PT_SETDBREGS(dr7=0xdeadbeef00000000) ACCEPTED by kernel
[*] PT_CONTINUE -> tracee switches in -> movq %rax,%dr7
<ssh dies>
vm.sh status -> down
serial: Fatal trap 9: general protection fault while in kernel mode
         current process = 1136          <- the traced child
         Stopped at cpu_heavy_restore+0x1ab: movl %eax,%dr7
         db>  (all CPUs stopped)
```

Baseline control on the same kernel: dr7=0 accepted, tracee resumed, guest
healthy (run.log). The panic is caused specifically by the unvalidated
reserved bits of dr7 surviving into the context-switch DR7 load.

## Exploitability ceiling

This is a clean #GP(0) fatal trap, not a memory-corruption primitive: no
attacker-controlled write, no state survives the trap (system stops in ddb /
reboots). The ddb prompt is on the serial console only — an ssh-local
attacker cannot drive it. Under KVM and on Intel bare metal the DoS is
deterministic; AMD bare metal ignores writes to DR7[63:32] (no fault) and is
not affected by this variant. Ceiling: unprivileged local kernel panic (DoS).
Impact class: panic/dos — no uid0 route exists for this bug.

Note the GD bit (dr7 bit 13) *is* accidentally defused by the swtch.s
`0x0000fc00` mask (taken from the in-CPU dr7, always GD=0), so the classic
"debug-register-access #DB in kernel" escalation of this family does NOT
apply here — only the reserved-high-bits #GP does.

## Fix validation

`fix.diff` adds to set_dbregs() (machdep.c, before the R/W loop):

```c
if (dbregs->dr[7] & 0xFFFFFFFF00000000LU)
        return (EINVAL);
```

- Rebuilt the guest kernel with only this change
  (`make nativekernel KERNCONF=X86_64_GENERIC`, BUILD_RC=0), installed,
  rebooted (`#1: Fri Oct 9 11:27:30 UTC 2026`).
- Exact same PoC on patched kernel: `PT_SETDBREGS: Invalid argument`, guest
  stays up (fix_run.log).
- Regression on patched kernel: dr7=0 and dr7=0x401 still accepted, tracee
  resumes, no panic (fix_run.log).
- Guest reset to clean snapshot afterwards.

## Distinction from known findings

- DF-3258 / DF-0993: dbregs dr[8..15] *read-side* uninit leak — different
  direction, different sink; not re-reported.
- DF-1315: guest DR persistence across #VMEXIT (vkernel host interaction) —
  different mechanism entirely.
- DF-5335: bad-%ss iretq fault machine (sigreturn/set_regs) — different fault
  site (doreti_iret) and different unvalidated field (tf_ss); not re-reported.
- The vkernel64 twin implements set_dbregs as ENOSYS (cpu_regs.c:788-792), so
  no twin divergence weakens this analysis.

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