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)
PoC verification
Evidence pack
findings/poc/DF-3258 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
sys/cpu/x86_64/include/reg.h:89-96βstruct dbreg { unsigned long dr[16]; }with indices 8-15 documented "reserved".sys/platform/pc64/x86_64/machdep.c:3104-3129βfill_dbregs()assignsdr[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.)sys/vfs/procfs/procfs_dbregs.c:57βstruct dbreg r;lives on the kernel stack uninitialized;procfs_dbregs.c:65-67fills it viaprocfs_read_dbregsβfill_dbregs, thenuiomove_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).sys/kern/sys_process.c:546-555βPT_GETDBREGSconstructs asizeof(struct dbreg)-sized kernel uio over the caller's buffer and calls the sameprocfs_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_SETDBREGSpath 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
fixedfix.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
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/
No comments yet.