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

fill_dbregs leaves dr[8]-dr[15] uninitialized - 64-byte kernel stack leak via /proc/<pid>/dbregs and PT_GETDBREGS

Summary

fill_dbregs at machdep.c:3104-3130 only writes dr[0]-dr[7] (64 of 128 bytes). dr[8]-dr[15] (8*8=64 bytes) never written in either DDB or ptrace/procfs path. struct dbreg is unsigned long dr[16]=128 bytes. procfs_dbregs.c:57 declares struct dbreg r WITHOUT zeroing; uiomove_frombuf copies all 128 bytes to userspace -> 64 bytes uninitialized kernel stack leaked per read. Same class as DF-0938 (fpregs). Corrects DF-0938 claim that dbregs fully populated (it is NOT). Reachable by any local user via /proc/self/dbregs (p_trespass returns 0 for self). KASLR bypass: leaked bytes routinely contain kernel .text return addresses, proc/ucred/vmspace pointers. Fix: memset(&dbregs->dr[8],0,sizeof(dr[8])*8) in fill_dbregs.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0993 Β· 15 files
FileTypeDescriptionSize
dbregs_leak.c trigger-source PoC: reads /proc/self/dbregs and dumps dr[0..15] 3.6 KB view raw
build.sh build-script cc -O -o dbregs_leak dbregs_leak.c 172 B view raw
run.sh run-script single-dump + fork-variance modes 234 B view raw
build.log build-log final successful build 106 B view raw
run.log run-log decisive unpatched run, dr[8..15] non-zero 842 B view raw
leak_sample.txt leak-sample 3 successive runs showing residue variance 2.5 KB view raw
baseline_run.log run-log fresh with-src baseline leak confirmation 831 B view raw
fix.diff suggested-fix zero dr[8..15] in fill_dbregs 568 B view raw
fix_build.log build-log nativekernel build of patched kernel 5.6 MB ↓ download
fix_run.log run-log 3 runs on patched kernel #1: all clean 2.5 KB view raw
env.txt environment uname, cc version 188 B view raw
VERDICT.md verdict full narrative 4.3 KB ↓ raw
README.md readme how to reproduce 1.2 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme how to reproduce
↓ download raw

DF-0993 β€” fill_dbregs() uninitialized dr[8]-dr[15] leak

Summary

fill_dbregs() at sys/platform/pc64/x86_64/machdep.c:3104-3130 populates only dr[0]-dr[7] (64 bytes) of struct dbreg (128 bytes, sys/cpu/x86_64/include/reg.h:89). procfs_dbregs.c:57 declares struct dbreg r; with no zero-initialization, then uiomove_frombuf copies all 128 bytes back to userspace, leaking 64 bytes of uninitialized kernel stack per read.

Same class as DF-0938 (fpregs leak). Reachable by any local user via /proc/self/dbregs (the p_trespass check returns 0 for self, no privilege needed) or via ptrace(PT_GETDBREGS) on a child.

Build

cc -O -o dbregs_leak dbregs_leak.c

Run

./dbregs_leak           # 3 rounds
./dbregs_leak 5 -v      # 5 rounds, verbose hex dump

Expected (bug present)

  • dr[8]-dr[15] are non-zero in at least some rounds (LEAK marker).
  • Bytes vary across rounds (variance: N of 8 high dwords differ) β€” proof that they are live uninitialized stack residue, not a fixed sentinel.
  • Sample residue frequently contains values that look like kernel pointers (high bits set) or kernel .text return addresses.

Expected (fixed)

  • dr[8..15] are 0x0 in every round (no residue, no variance).
VERDICT.md verdict full narrative
↓ download raw

DF-0993 β€” fill_dbregs() uninitialized dr[8]-dr[15] leak

Verdict

REPRODUCED + FIX VALIDATED. Uninitialized kernel stack residue is leaked through /proc/<pid>/dbregs (and PT_GETDBREGS). The single-line fix (memset/loop-zero dr[8..15] in fill_dbregs) closes the leak; verified by building and booting a single-fix kernel.

Mechanism (cited)

struct dbreg is unsigned long dr[16] = 128 bytes (sys/cpu/x86_64/include/reg.h:89-96). Architecture defines only dr[0]–dr[7]; dr[8]–dr[15] are reserved.

fill_dbregs() at sys/platform/pc64/x86_64/machdep.c:3104-3130 populates only dr[0]–dr[7] (64 bytes). The reserved dr[8]–dr[15] (64 bytes) are never written.

procfs_dodbregs() at sys/vfs/procfs/procfs_dbregs.c:51-77 declares struct dbreg r; at line 57 without zero-initialization, then calls procfs_read_dbregs(lp, &r) β†’ fill_dbregs(lp, &r) (only 64 bytes written), and finally uiomove_frombuf(&r, sizeof(r), uio) at line 67 copies the full 128-byte struct back to userspace β€” leaking the 64 uninitialized bytes from the kernel stack.

p_trespass(curp->p_ucred, p->p_ucred) returns 0 for self (procfs_dbregs.c:62), so the leak is reachable by any unprivileged local user via /proc/self/dbregs. The ptrace(PT_GETDBREGS) path goes through the same procfs_dodbregs (sys/kern/sys_process.c:537-559).

Same bug class as DF-0938 (fpregs); DF-0993 corrects the DF-0938 note that "dbregs is fully populated" β€” it is not.

Exploit chain / Impact

  • Class: info leak (uninitialized kernel memory to userspace).
  • No escalation chain β€” this is a pure read primitive (no write, no corruption). The realistic impact ceiling is KASLR-assist / pointer leak: the leaked bytes routinely contain canonical kernel virtual addresses (0xfffff8XX_XXXXXXXX) and other stack residue from the syscall return path. This is exactly the kind of residue an attacker uses to defeat KASLR or to refine slab-grooming offsets for a separate memory-corruption primitive.
  • CVSS as filed: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N (Medium).

PoC

  • dbregs_leak.c β€” opens /proc/self/dbregs, reads 128 bytes, dumps all 16 dr[] values. Mode 0 = single verbose dump; mode 1 = fork-and-read across fresh kernel threads to demonstrate variance.
  • build.sh β€” cc -O -o dbregs_leak dbregs_leak.c
  • run.sh β€” runs both modes.

Observed output on the unpatched 6.5-DEVELOPMENT #0 baseline

dr[ 8] = 0xfffff800905fc780   <-- UNINITIALIZED (should be 0)
dr[ 9] = 0x0000000000000400   <-- UNINITIALIZED (should be 0)
dr[10] = 0x0000000000000000   <-- UNINITIALIZED (should be 0)
dr[11] = 0x00000000807093d0   <-- UNINITIALIZED (should be 0)
dr[12] = 0x000005c000000000   <-- UNINITIALIZED (should be 0)
dr[13] = 0xfffff8008d9fade0   <-- UNINITIALIZED (should be 0)
dr[14] = 0xfffff80116aa90a8   <-- UNINITIALIZED (should be 0)
dr[15] = 0xfffff80116aa8c80   <-- UNINITIALIZED (should be 0)
[verdict] dr[8..15] residue_or=0xfffffdc19ffffff8 LEAK
  • Bytes vary across program executions (see leak_sample.txt) β€” confirming live uninitialized stack residue rather than a fixed sentinel.
  • The values are canonical kernel pointers in the 0xfffff8XX_XXXXXXXX range β€” direct KASLR-bypass / kernel-base material.

Fix

fix.diff β€” adds a loop in fill_dbregs() that zeros dr[8..15] before the rest of the function runs. Minimal and targeted at the root cause.

The single-fix kernel (X86_64_GENERIC #1, built from the patched source) was booted and the PoC re-run three times: dr[8..15] are all-zero on every run (residue_or=0x0 clean). See fix_run.log and fix_build.log.

Files in this evidence pack

  • dbregs_leak.c / dbregs_leak β€” PoC source + binary
  • build.sh, run.sh β€” exact reproduce commands
  • build.log β€” compiler output (final successful build)
  • run.log β€” decisive run on unpatched kernel
  • leak_sample.txt β€” 3 successive runs on the baseline showing variance
  • baseline_run.log β€” fresh vm.sh reset with-src baseline run (the "before")
  • fix.diff β€” minimal fix (zero dr[8..15] in fill_dbregs)
  • fix_build.log β€” full nativekernel build output of the patched kernel
  • fix_run.log β€” 3 runs on the patched kernel (#1) β€” all clean
  • env.txt β€” guest environment
  • manifest.json β€” artifact catalog

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sun Jul 19 15:05:01 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live). fill_dbregs dr[8-15] uninitialized -> 64B kernel stack leak with KVA ptrs. Unprivileged /proc/self/dbregs. Kernel rebuild fix.