DragonFlyBSD Kernel Audit
DF-2728 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2728",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Reproduced on the stock INVARIANTS guest (X86_64_GENERIC, 6 cpus): remotely scheduling a td_preemptable thread via the generic _lwkt_schedule() remote branch makes lwkt_schedule_remote() drop the critical section inside framed-IPI dispatch (gd_processing_ipiq == 1) and enter lwkt_preempt(), whose KKASSERT(gd->gd_processing_ipiq == 0) at lwkt_thread.c:1026 panics with the exact predicted backtrace (lwkt_process_ipiq_frame -> lwkt_process_ipiq_core -> lwkt_schedule_remote -> lwkt_preempt, cpuid=1). The control mode and the first (non-framed) run show the identical sequence completes cleanly whenever lwkt_preempt() is not entered, proving the panic is solely the assert contradicting a designed path, not state corruption. INVARIANTS builds only; no stock-kernel impact; no escalation primitive.",
  "exploit_chain": "",
  "evidence": [
    "panic.txt: 'panic: assertion \"gd->gd_processing_ipiq == 0\" failed in lwkt_preempt at /usr/src/sys/kern/lwkt_thread.c:1026', 'cpuid = 1', four-frame backtrace through lwkt_schedule_remote and lwkt_process_ipiq_frame",
    "panic.txt: 'df2728: mode3: ok, no panic (control)' and 'mode1: returned WITHOUT panic (non-INVariANTS semantics: designed preempt)' - clean counterfactuals",
    "VERDICT.md: full crit/intr-nesting/ipiq accounting trace showing the stock path is balanced by design",
    "df2728.c: trigger drives exactly _lwkt_schedule()'s remote branch (lwkt_thread.c:1309-1311)"
  ],
  "kernel_refs": [
    "sys/kern/lwkt_thread.c:1026",
    "sys/kern/lwkt_thread.c:1336-1348",
    "sys/kern/lwkt_thread.c:1274-1314",
    "sys/kern/lwkt_thread.c:1042-1057",
    "sys/kern/lwkt_ipiq.c:605",
    "sys/platform/pc64/apic/apic_vector.s:337-358",
    "sys/kern/kern_intr.c:336"
  ],
  "poc_changes": "Trigger authored from scratch (no seed PoC existed): KLD creates a TDF_NOSTART thread homed on cpu1 with td_preemptable=lwkt_preempt and td_pri=TDPRI_INT_MED (state of a sleeping interrupt thread), plus a sysctl trigger that calls lwkt_schedule() from cpu0. First attempt did not panic because cpu1 was idle (no framed preempt context); fixed by pinning a userland spinner to cpu1 so the Xipiq lands on a critcount-0 user thread.",
  "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": 420,
  "guest_dirty": 0,
  "build_cmd": "cd /tmp/df2728 && make obj && make",
  "run_cmd": "usched :1 kldload /usr/obj/tmp/df2728/df2728.ko && (usched :2 /usr/local/bin/spin &) && usched :1 sysctl -n debug.df2728=3 && usched :1 sysctl -n debug.df2728=1",
  "code_hash": "03fe02857aec78f1a20c4fa82ee471de2fad6cc6db04d42cb46fc4240a10e2d5",
  "notes": "INVARIANTS-only panic (Low). Unprivileged userland cannot reach it on the stock configuration because in-tree interrupt-thread wakeups are same-cpu; the assert nevertheless forbids a state the exported lwkt_schedule() API and lwkt_schedule_remote() explicitly support (KLDs/drivers/future in-tree users), and it prevents INVARIANTS kernels from exercising the designed remote-preemption path at all. Guest reset with-src (clean) after verification.",
  "recommended_fix": "Remove the KKASSERT(gd->gd_processing_ipiq == 0) from lwkt_preempt() (lwkt_thread.c:1026); preemption from framed-IPI dispatch is designed behavior of lwkt_schedule_remote().",
  "fix_status": "not_testable",
  "fix_kernel_uname": "",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff removes the contradicting assert (git apply --check clean against sys/). Kernel rebuild validation not performed: Low / INVARIANTS-only finding with no stock-kernel effect - the control run and first non-framed mode-1 run already demonstrate the identical call sequence completes cleanly whenever the assert is not compiled in or not reached.",
  "fix_evidence": ["fix.diff (git-apply clean)", "run.log: control + non-framed counterfactuals"]
}