β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3258

Uninitialized kernel-stack disclosure of dr[8..15] (64 bytes) via /proc/<pid>/dbregs and ptrace(PT_GETDBREGS)

Summary

struct dbreg is unsigned long dr[16] (128 bytes) but fill_dbregs() initializes only dr[0..7] (dr[4]/dr[5] zeroed, dr[8..15] never written). procfs_dodbregs() declares 'struct dbreg r;' uninitialized on the kernel stack and copies all 128 bytes to userspace via uiomove_frombuf. ptrace(PT_GETDBREGS) funnels through the same function with a full-size uio, leaking identically. Contrast: fill_regs bcopy's the entire struct reg from the trapframe and fill_fpregs the whole save area - fill_dbregs is the only partial initializer. VERIFIED 5 runs as uid=1001: kernel text/data/heap/stack pointers (fffff800905f7980, fffff8011830f7f8, ffffffff8064ced0), 33-47/64 non-zero bytes across runs - KASLR defeat and kernel-data reconnaissance; vkernel64 twin has the same bug. After the one-line fix (bzero at the fill_dbregs chokepoint, in-guest nativekernel rebuild): 0/64 non-zero on both paths across 2 runs. Any unprivileged process leaks 64 bytes of its own thread's kernel stack per read, at will.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3258 Β· 11 files
FileTypeDescriptionSize
dbregs_leak.c β€” 3.2 KB view raw
build.sh β€” 89 B view raw
run.sh β€” 94 B view raw
run.log β€” 767 B view raw
run.2.log β€” 1.2 KB view raw
run.patched.log β€” 1.5 KB view raw
leak_sample.txt β€” 1.1 KB view raw
kbuild-tail.log β€” 393 B view raw
fix.diff β€” 498 B view raw
VERDICT.md β€” 4.0 KB ↓ raw
env.txt β€” 122 B view raw
VERDICT.md
↓ download raw

DF-3258 VERDICT β€” uninitialized dr[8..15] disclosure via /proc//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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src, make -j6 nativekernel RC=0 + installkernel, reboot: dr[8..15] zeroed on both procfs and ptrace paths (2 runs, 0/64 non-zero), fifo/rlimit smoke unchanged. Leak eliminated; dr[0..7] semantics and PT_SETDBREGS unaffected.

run.patched.log: 0/64 non-zero dr[8..15] on patched kernel, both paths
kbuild-tail.log: 'Kernel build for X86_64_GENERIC completed' RC=0
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 9 17:33:46 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: dr[8..15] = fffff8008ed94580 / fffff80116c5a8e0 ... 37-40/64 non-zero (baseline kernel)
run.2.log: stable kernel pointers across runs, 33-47/64 non-zero
leak_sample.txt: 5 samples, kernel text/data/heap addresses
run.patched.log: 0/64 non-zero on procfs+ptrace after fix.diff kernel (2 runs)
kbuild-tail.log: nativekernel RC=0 + installkernel

PoC changes

none β€” seed-equivalent trigger written fresh; added stack-priming helper and ptrace second path

Verified recommended fix

bzero(dbregs, sizeof(*dbregs)) at the top of fill_dbregs() (mirrored in the vkernel64 twin)

Verdict

Uninitialized kernel-stack disclosure of dr[8..15] (64 bytes) on every full read of /proc//dbregs and every ptrace(PT_GETDBREGS): fill_dbregs (machdep.c:3104-3129) initializes only dr[0..7] while procfs_dodbregs copies all 128 bytes of an uninitialized stack struct out. Reproduced 3/3 baseline runs as unprivileged uid=1001 with 33-47/64 non-zero bytes including kernel text/heap/stack pointers (fffff800905f7980, ffffffff8064ced0); after bzero fix in fill_dbregs, rebuilt kernel (#1 Sep 9) shows 0/64 on both paths across 2 runs with no regressions.