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)
PoC verification
Evidence pack
findings/poc/DF-5335 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedBaseline 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
Confirmed kernel references
- sys/platform/pc64/x86_64/trap.c:679-682
- sys/platform/pc64/x86_64/ipl.s:206-240
- sys/platform/pc64/x86_64/exception.S:351-370
- sys/cpu/x86_64/include/asmacros.h:170-213
- sys/platform/pc64/x86_64/machdep.c:1033-1041
- sys/platform/pc64/x86_64/machdep.c:3025-3028
- sys/platform/pc64/include/pcb.h
- sys/platform/pc64/x86_64/swtch.s:366
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.
No comments yet.