⬢ DragonFlyBSD Kernel Audit
DF-5697 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5697",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "Reproduced against the verbatim sys/libkern/qsort.c in the userland rig (host zig cc 18.1.6 AND DragonFly guest gcc 8.3): with caller-supplied es == 0 and n >= 7 the sort never terminates - every pointer advance is a multiple of es == 0, so the partition loop (qsort.c:136-159) spins forever calling the comparator with pb frozen (5,000,000+ comparator calls observed for n=64, which needs <3000) - and the very first pivot swap (qsort.c:132, pm == a because the n<7 insertion path is skipped) drives swapfunc(a, a, 0, 1) whose swapcode do-while (qsort.c:48-57) executes its body once at n == 0, an out-of-contract 8-byte load/store at the zero-length arrays base (proven by SIGSEGV with base inside a PROT_NONE page, before any comparator call). n <= 6 returns cleanly; the two-line if (es == 0) return; guard eliminates both symptoms (guard-only variant f7 returns for n=0..8 with 0 comparator calls and no fault). No in-tree caller passes es == 0 (all five call sites use sizeof-constants: ext2_htree.c:587, linux_sort.c:36, vinumio.c:760, ng_ppp.c:1747, ng_ppp7:2292), so this is hardening of a core kernel primitive - severity Info; the demonstrated artifact is a non-terminating kernel-context spin (impact dos) should any future caller derive es from untrusted input.",
  "exploit_chain": "",
  "evidence": "run.log (host): ES0 HANG CONFIRMED: comparator called 5,000,000 times with es=0, ES0 ACCESS CONFIRMED (verbatim): SIGSEGV inside kqsort with es=0 n=7, es0_segv(2): child exited st=0 (no fault) for the guard-only fix, ALL_EXPECTATIONS_MET; run_es0.log (guest gcc 8.3, same symptoms); VERDICT.md (mechanism + boundary n<=6 vs n>=7); fix.diff (2-line guard)",
  "kernel_refs": [
    "sys/libkern/qsort.c:48-57",
    "sys/libkern/qsort.c:62-69",
    "sys/libkern/qsort.c:71-77",
    "sys/libkern/qsort.c:113-119",
    "sys/libkern/qsort.c:132",
    "sys/libkern/qsort.c:136-159",
    "sys/libkern/qsort_r.c:9"
  ],
  "poc_changes": "Authored from scratch (no seed): fork+waitpid SIGSEGV proof for the 8-byte access (an ASan build was impossible on this host - no system cc and the zig/musl ASan runtime lacks __asan_stack_malloc; the PROT_NONE page proof is equally decisive and runs on the guests native cc too), comparator-call watchdog exit for the hang proof, guard-only fixed variant qsort_fixed7.c to isolate the fix ingredient from DF-5696s type fix.",
  "attempts": 2,
  "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": 95,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh (host zig cc; guest build uses the DF-5696 packs guest_build.sh pattern)",
  "run_cmd": "sh run.sh",
  "code_hash": "7bf6733a06a63fe243efca8622f5cd46c8cda0d88072b7c4cea7752097a155a7",
  "notes": "exit code 7 = hang confirmed, es0_segv rc=0 = SIGSEGV confirmed. The 8-byte self-swap writes back the same value on writable pages - it is an out-of-contract access/UB, not value corruption; the hang is the practical kernel impact. Guest es0 evidence was collected during the DF-5696 guest session (run_es0.log); guest has since been reset with-src.",
  "recommended_fix": "Reject the degenerate element size at function entry: if (es == 0) return; before the loop label in the shared qsort.c body (covers kqsort and kqsort_r).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "A/B on identical verbatim source: without the guard, es=0 n>=7 hangs (5M+ comparator calls) and faults on the 8-byte access (SIGSEGV at the PROT_NONE base); with the 2-line guard only (qsort_fixed7.c), n=0..8 all return immediately with 0 comparator calls and no memory access. A patched-kernel build is not applicable: no in-tree caller can pass es==0, so a kernel build would show no observable delta.",
  "fix_evidence": "run.log: hang rc=7 and SIGSEGV on verbatim; guarded f7 (es==0 guard only) es=0 n=0..8: returned, cmp_calls=0 and es0_segv(2): child exited st=0 (no fault); fix.diff"
}