โฌข DragonFlyBSD Kernel Audit
DF-2898 / verdict.json
โ† back to finding โ†“ download raw
{
  "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"
  ]
}