DF-2898 / verdict.json
{ "finding_id": "DF-2898", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "A single unprivileged libc call syscall(SYS_syscall, 0) deterministically panics the stock DragonFly x86_64 kernel: sysent[0] and sysent[198] (init_sysent.c:17,215) both dispatch sys_xsyscall, which re-reads the target number from the never-changing tf_rdi (trap.c:1402-1406) and re-dispatches itself without a recursion guard until the 16KB kernel stack guard page is hit (DOUBLE FAULT, rip=sys_xsyscall+0x84, verified 5x across 3 boots incl. twice via plain libc). Instrumented-kernel tracing proved entry stack geometry is healthy on every indirect syscall (no stack leak) and the ~10.5KB is consumed strictly inside the one dispatch, i.e. pure recursion. Ceiling is availability loss (return addresses only, stopped by the guard); no corruption beyond the guard, no uid=0 path. Two-line guard remapping code in {0,198} to SYS___nosys validated on a rebuilt guest kernel: all trigger variants now get SIGSYS, valid indirect dispatch (rax=0/rdi=20 -> getpid) still returns correct results, guest stays up.", "exploit_chain": "unpriv user -> libc syscall(SYS_syscall,0) (rax=198 stub, number left in rdi) -> syscall2 -> sysent[198].sy_call = sys_xsyscall -> code = tf_rdi = 0 -> sysent[0].sy_call = sys_xsyscall -> identical re-entry forever -> kernel stack guard page -> double fault -> panic (machine dead in ddb). No escalation path: the recursion writes only return addresses downward into the guard page; it is a pure local DoS.", "evidence": [ "findings/poc/DF-2898/panic-baseline.txt โ stock-kernel panic transcripts (2 independent boots + instrumented probe run), rip=sys_xsyscall+0x84, rsp page-aligned on the kstack guard", "findings/poc/DF-2898/run-patched.txt โ fixed kernel: all 7 trigger variants -> SIGSYS, no panic, P1 valid-indirect still returns pid, guest up", "findings/poc/DF-2898/trigger.c โ one-line canonical PoC", "findings/poc/DF-2898/README.md โ full mechanism incl. libc rax=198 no-shift stub disassembly and SYSE instrumentation data (healthy entry rsp, ~10.5KB consumed inside one dispatch)", "findings/poc/DF-2898/fix.diff โ validated 2-line guard (pc64 + vkernel64)" ], "kernel_refs": [ "sys/kern/init_sysent.c:17", "sys/kern/init_sysent.c:215", "sys/platform/pc64/x86_64/trap.c:1402", "sys/platform/pc64/x86_64/trap.c:1445", "sys/platform/vkernel64/x86_64/trap.c:1230", "sys/kern/init_main.c:341" ], "poc_changes": "Original harness (sysent_edge.c stage 5) crashed via libc syscall(SYS_syscall,197,...); distilled to the minimal one-line trigger syscall(SYS_syscall,0); intermediate raw-asm stubs had a compiler register-allocation artifact (input bound to RAX, clobbered by movl $0,%eax -> rdi=0) that took the same kernel path โ replaced with explicit-register asm in probe.c; added libc/no-libc matrix to prove independence from my asm.", "attempts": 9, "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", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cc -O0 -o trigger trigger.c", "run_cmd": "./trigger (as unprivileged user; expect kernel panic)", "code_hash": "a24e67fc8028b52aabe662298b9a70daf2373b5bf19a6e917ff5af7ce78d1930", "notes": "Guest source tree verified bit-identical to the audited repo (md5 trap.c fa54899450dc815d52a503319315154e, init_sysent.c a58e1fe6a06d2acce88097d691da272d). Diagnostic kernels (kprintf instrumentation) were built and reverted; final state: guest reset to clean with-src snapshot. sysent_xcheck.py in the pack is the pass-2 machine cross-check proving the rest of the table sound (556 rows == SYS_MAXSYSCALL == syscallnames[], all AS() structs exist in sysproto.h and union sysunion, max narg 7 == union capacity 7 so extargs copyin cannot overflow, no NULL sy_call, all literal-0-narg entries have dummy-only structs).", "recommended_fix": "In sys_xsyscall (pc64 and vkernel64 trap.c), after the sv_size bounds remap add: else if (code == SYS_syscall || code == SYS___syscall) code = SYS___nosys; โ mirrors FreeBSD's indirect-handler guard, validated to fully stop the panic while preserving valid indirect dispatch.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Thu Sep 3 03:11:47 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (pristine + fix.diff only)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Rebuilt in-guest from pristine source plus the 2-line guard; the exact baseline trigger set (probe 2-8, incl. the original stage-5 crasher) now yields SIGSYS with the guest healthy, while a valid indirect getpid via rax=0/rdi=20 still returns the pid โ bug eliminated, no regression.", "fix_evidence": [ "findings/poc/DF-2898/run-patched.txt", "findings/poc/DF-2898/build.log", "findings/poc/DF-2898/fix.diff" ] } |