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)
PoC verification
Evidence pack
findings/poc/DF-0993 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 (LEAKmarker).- 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
.textreturn addresses.
Expected (fixed)
dr[8..15]are0x0in every round (no residue, no variance).
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 16dr[]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.crun.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_XXXXXXXXrange β 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 + binarybuild.sh,run.shβ exact reproduce commandsbuild.logβ compiler output (final successful build)run.logβ decisive run on unpatched kernelleak_sample.txtβ 3 successive runs on the baseline showing variancebaseline_run.logβ freshvm.sh reset with-srcbaseline run (the "before")fix.diffβ minimal fix (zerodr[8..15]infill_dbregs)fix_build.logβ fullnativekernelbuild output of the patched kernelfix_run.logβ 3 runs on the patched kernel (#1) β all cleanenv.txtβ guest environmentmanifest.jsonβ artifact catalog
Fix verification
fixedvalidated
see evidence pack
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.
No comments yet.