DF-5657 / verdict.json
{ "finding_id": "DF-5657", "status": "reproduced", "verdict": "set_dbregs() (sys/platform/pc64/x86_64/machdep.c:3133, reached from procfs_machdep.c:97-101 and PT_SETDBREGS via sys/kern/sys_process.c:531) validates only dr7s R/W fields and dr0-dr3 addresses; it stores the raw 64-bit dr[7] into pcb_dr7 (machdep.c:3220). cpu_heavy_restore() reloads %dr7 masking only bits 10-15 (swtch.s:612-617), so reserved bits 63:32 flow into movq %rax,%dr7. On Intel (and deterministically under KVM via the host kvm_set_dr check) that raises a ring-0 #GP mid-context-switch; the fault is not at doreti_iret so trap.c:675-681 does not apply and it falls to trap_fatal() (trap.c:770). Verified as uid 1001 on the stock guest: ptrace(PT_SETDBREGS, dr7=0xdeadbeef00000000) is accepted, PT_CONTINUE panics the kernel at cpu_heavy_restore+0x1ab (movl %eax,%dr7) with current process = the traced child; baseline dr7=0 does not crash. Clean fatal trap, no write primitive; ceiling is unprivileged local kernel panic/DoS (no uid0 route; ddb prompt is console-only). Fix (reject dr7 with bits 63:32 set in set_dbregs) validated on a rebuilt kernel: same PoC now gets EINVAL, guest stays up, dr7=0/0x401 still accepted.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "unprivileged user fork()s a child, PT_TRACE_ME + raise(SIGSTOP); parent waitpid(), ptrace(PT_SETDBREGS) with dr[7]=0xdeadbeef00000000 (passes the R/W-field loop because bits 16-29 are 0 and the address checks because no breakpoint is enabled); kernel stores raw dr7 into pcb_dr7 and sets PCB_DBREGS; ptrace(PT_CONTINUE) schedules the tracee; cpu_heavy_restore loads %dr7 with the reserved high bits intact; KVM/Intel raise #GP(0) in ring 0 inside the context switch; trap_fatal() -> fatal trap 9, all CPUs stopped, system down. (DoS only - no memory write primitive, no uid0 escalation path.)", "evidence": "panic.txt: Fatal trap 9 ... Stopped at cpu_heavy_restore+0x1ab: movl %eax,%dr7, current process 1136 (uid-1001 tracee). run.2.log: PT_SETDBREGS(dr7=0xdeadbeef00000000) ACCEPTED then guest down. run.log: baseline dr7=0 accepted, no crash, guest up. fix_run.log: patched kernel rejects attack with EINVAL, accepts dr7=0/0x401, guest up. fix_build.log: nativekernel BUILD_RC=0 with fix.diff.", "kernel_refs": [ "sys/platform/pc64/x86_64/procfs_machdep.c:97", "sys/platform/pc64/x86_64/machdep.c:3133", "sys/platform/pc64/x86_64/machdep.c:3166", "sys/platform/pc64/x86_64/machdep.c:3220", "sys/platform/pc64/x86_64/machdep.c:3222", "sys/platform/pc64/x86_64/swtch.s:597", "sys/platform/pc64/x86_64/swtch.s:612", "sys/platform/pc64/x86_64/swtch.s:617", "sys/platform/pc64/x86_64/trap.c:675", "sys/platform/pc64/x86_64/trap.c:770", "sys/kern/sys_process.c:531", "sys/vfs/procfs/procfs_dbregs.c:66" ], "poc_changes": "No seed PoC existed for this pass-2 finding; written fresh. Guest-side trivia: PT_SETDBREGS is 38 on pc64 (sys/platform/pc64/include/ptrace.h: PT_FIRSTMACH+6) and struct dbreg is dr[16] u64s, so the PoC passes its own 128-byte struct directly.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (QEMU/KVM, -smp 6 -m 4096)", "runtime_sec": 4400, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/poc /tmp/poc.c", "run_cmd": "/tmp/poc 1 0 # baseline; then /tmp/poc 2 # attack dr7=0xdeadbeef00000000", "code_hash": "d1ee3961104b4e3bddcc6eb539ad58670d6eb219aa051e03b4d70573e3422f0f", "notes": "Known findings not re-reported: DF-1056 (fpregs 108-of-512 fill), DF-0937 (SSTOP vs LSSTOP divergence), DF-0938 (fpregs leak family), DF-3258 (dr[8..15] disclosure). The bad-%ss-via-set_regs shape is DF-5335s territory (its DB record already names set_regs and was guest-verified) - not re-reported. The GD bit (dr7 bit 13) is accidentally defused by the swtch.s 0xfc00 merge mask; only the reserved bits 63:32 fault. AMD bare metal ignores writes to DR7[63:32]; KVM (this guest, and most virtualized deployments) and Intel bare metal #GP deterministically.", "recommended_fix": "Reject DR7 values with reserved high bits in set_dbregs(): if (dbregs->dr[7] & 0xFFFFFFFF00000000LU) return (EINVAL); (fix.diff)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (reject dr7 with bits 63:32 set in set_dbregs) applied to the guest /usr/src, kernel rebuilt with make nativekernel KERNCONF=X86_64_GENERIC (BUILD_RC=0), installed, rebooted. Baseline behavior on stock kernel reproduced first (fatal trap 9 at cpu_heavy_restore+0x1ab). On the patched kernel the exact PoC gets PT_SETDBREGS: Invalid argument and the guest stays up; regression runs with dr7=0x0000000000000000 and dr7=0x0000000000000401 are still accepted and resume cleanly. Bad behavior gone, no functional regression.", "fix_evidence": "fix_run.log (attack -> EINVAL, regressions accepted, vm.sh status up); fix_build.log (patch applied hunk#1 @3153, BUILD_RC=0, uname #1 Fri Oct 9 11:27:30 UTC 2026); fix.diff (git-applyable, machdep.c set_dbregs).", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Oct 9 11:27:30 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64" } |