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

doreti_iret fault state machine is espfix64-class broken: sigreturn with %ss resolving to the null GDT descriptor or an absent LDT descriptor hard-crashes the machine from unprivileged userland (silent CPU reset or SMP livelock, no panic); subclasses that do reach C code get their frame rebuilt from PCB padding/stale stack

Summary

sigreturn/set_regs validate only %cs: a restored %ss naming the null GDT descriptor or an absent LDT descriptor makes the CPL-changing iretq fault AFTER the CPU committed the broken SS:RSP - the exception delivery itself dies below panic(): silent hard reset (CPU#0 in real mode at BIOS) or whole-machine livelock (faulting CPU spins intr-off, others block in smp_inval_intr). Handled subclasses rebuild tf_rsp/tf_ss rsp-relative from PCB padding/stale kstack (classification + delivered ucontext from uninit kernel stack). The Linux espfix64/CVE-2014-9090 analog. Guest-verified 6/6 AS UID 1001 on stock kernel (ss=0xdead reset 3/3, ss=0x4 wedge 2/2, ss=0x7 reset 1/1; ss=0/0x1b/0x10/0x1000/noncanonical-rsp yield SIGBUS masking the bug); fix (SS_SECURE+rsp-range gate in sigreturn/set_regs + deterministic user-frame SIGBUS at the doreti_iret redirect) validated on rebuilt kernel: former crash values now SIGBUS, guest up, valid sigreturns unaffected, 9/9 rejected in dmesg. No privilege route (stale-data read feeds only the victim's own context) - total-system DoS ceiling.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5335 Β· 16 files
FileTypeDescriptionSize
iretbug.c β€” 2.0 KB view raw
t8.c β€” 913 B view raw
build.sh β€” 159 B view raw
run.sh β€” 144 B view raw
run.log β€” 1.2 KB view raw
run_patched.log β€” 1.4 KB view raw
boot_final_crash_serial.log β€” 15.0 KB view raw
boot_v5_crash_serial.log β€” 26.4 KB view raw
boot_patched_crash_serial.log β€” 63.4 KB view raw
monitor_v1_reset.txt β€” 2.1 KB view raw
monitor_v5_reset.txt β€” 2.5 KB view raw
monitor_final_reset.txt β€” 1.8 KB view raw
panic.txt β€” 1.3 KB view raw
env.txt β€” 551 B view raw
fix.diff β€” 3.5 KB view raw
code_hashes.txt β€” 147 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched reproduced

Baseline re-confirmed (ss=0x4/0x7/0xdead crash the stock kernel). Patched kernel (kernel #4, fix.diff applied in-guest, nativekernel build): the same values now produce clean SIGBUS/exit-138 with the guest staying up across the full matrix; valid ss=0x23 sigreturns survive (SURVIVED exit=0); control variant unaffected; 9 'sigreturn: ss =' rejections in dmesg matching the 9 bad values exactly.

run_patched.log; fix.diff; build.log
↓ fix.diffper-fix-DF-5335

Confirmed kernel references

Detail

Exploit chain

unprivileged user -> raise a signal -> in the SA_SIGINFO handler rewrite the saved ucontext's mc_ss to 0x4 (or 0x7 / 0xdead) -> handler returns -> sigreturn bcopy's the frame (only %cs is validated) -> doreti iretq faults with the broken SS committed -> exception cascade below panic(): machine reset (QEMU monitor shows CPU#0 in real mode) or SMP wedge (smp_inval_intr storm). Impact ceiling is total system crash from any local user; no memory-corruption primitive found (the garbage-frame read is stale kstack/PCB padding, partially reflected into the victim's own ucontext only).

Evidence (decisive lines)

run.log (stock matrix: 6 SIGBUS then ss=0x4 -> instant reset, vm.sh status=down); monitor_final_reset.txt / monitor_v1_reset.txt (QEMU monitor: CPU#0 real mode CS=f000 CR0=0x10 after crash); boot_final_crash_serial.log (serial: nothing but boot loader after trigger - no panic); boot_v5_crash_serial.log (smp_inval_intr SMP-wedge manifestation); panic.txt (why no dump exists); run_patched.log (fixed kernel: 0x4/0x7/0xdead -> SIGBUS, guest up, valid ss=0x23 SURVIVES, 9 gate rejections in dmesg); fix.diff (validated, kernel #4 02:14:57)

PoC changes

iretbug.c (matrix driver) and t8.c (SSVAL-driven minimal injector) written from scratch; no seed PoC existed. Two intermediate trap.c-only repair attempts were built and honestly refuted by re-testing (the fault never reaches trap() for the crash family - proven with an instrumented kernel whose kprintf never fired); the validated fix gates mc_ss/mc_rsp in sys_sigreturn + set_regs.

Verified recommended fix

Validate mc_ss == GSEL(GUDATA_SEL, SEL_UPL) and canonical user mc_rsp in sys_sigreturn (and SS_SECURE in set_regs), rejecting with EINVAL + SIGBUS; replace trap.c's doreti_iret_fault redirect with a deterministic synthetic user frame.

Verdict

REPRODUCED: an unprivileged user crashes the whole machine (hard CPU reset or SMP livelock, no panic text, no dump) by restoring a signal context whose %ss resolves to the null GDT descriptor (0x4, 0x7) or an absent LDT descriptor (0xdead) via sigreturn. The CPL-changing iretq at doreti_iret commits the broken SS:RSP before the descriptor check faults, so the exception delivery itself uses the broken SS and dies below panic(); for subclasses that do reach C code, trap.c:679-682 redirects into doreti_iret_fault which rebuilds tf_cs/tf_rflags/tf_rsp/tf_ss from PCB padxx[] padding / stale stack (KMMUENTER_TFRIP rsp-relative read) - espfix64-class (cf. CVE-2014-9090). Determinism: ss=0xdead 3/3 resets, ss=0x4 2/2 wedges, ss=0x7 1/1; other bad-ss values (0, 0x1b, 0x10, 0x1000, non-canonical rsp) yield SIGBUS which is why the bug looks like a normal Bus error. Fixed and validated: SS_SECURE+canonical-rsp gate in sys_sigreturn and set_regs (plus a deterministic synthetic-frame path in trap.c replacing the doreti_iret_fault redirect) - all former crash values now SIGBUS with the guest up; valid ss=0x23 sigreturn unaffected.