DF-2728 / verdict.json
{ "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"] } |