DF-3738 / verdict.json
{ "finding_id": "DF-3738", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Mechanism PROVEN on the guest kernel by deterministic replica of if_emx.cs exact callout/serializer protocol; emx-specific runtime hardware-gated (inconclusive per family convention, same standard as the em(4) twin DF-3715). emx_timer (sys/dev/netif/emx/if_emx.c:1908-1931) is an MP callout whose callback BLOCKS in lwkt_serialize_enter(&sc->main_serialize) (:1914) and self re-arms inside the serializer (:1928); emx_stop tears it down with callout_stop(&sc->timer) (:2030) while holding ALL serializers (ASSERT_IFNET_SERIALIZED_ALL :2026; callers: SIOCSIFFLAGS :1227, emx_init :1354, emx_detach :989, emx_suspend :1059); the same stop-under-serializer exists in emx_intr_body LSC path (:1534, ISR holds main + skipmain serializers) and emx_npoll_status (:4196). DragonFly callout_stop synchronously waits for an in-progress callback (sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop sync=1, ssleep costp at :920) while lwkt_serialize_enter blocks (tsleep slize, sys/kern/lwkt_serialize.c:234). A callback dequeued and parked on the serializer therefore deadlocks against the stopper (AB-BA): both threads wedge permanently and the affected CPUs softclock thread stops delivering callouts. Kernel-module replica on the guest: stop under a held serializer with the emx_timer-shaped handler parked -> callout_stop NEVER returns (ps: 1105 D4 costp sysctl kern.emxreplica.stop_blk=1; ps -ax: (softclock 0) B0 slize; console output ends at the pre-callout_stop marker line). Negative control: the try-based emx_txgc_timer shape is fully unloaded by callout_stop (pending=0) - so the try-based gc timers done: re-arm (:448) cannot survive callout_stop post-processing on this kern_timeout.c, and no separate post-stop re-arm finding is filed (same judgment as em(4) pass 2). Fix validated at protocol level: replica of the fix.diff shape (try + 1-tick retry + double-check) completes the same stop cleanly (pending=0, clean kldunload).", "exploit_chain": "", "evidence": "findings/poc/DF-3738/VERDICT.md; run.log (replica session transcript + serial console + ps captures: stopper D3/D4 wchan costp = kern_timeout.c:920; softclock 0 B0 wchan slize = lwkt_serialize.c:234; fixed-shape validation callout_stop returned, pending=0); build.log (module built on guest, -Werror clean); fix.diff (try-enter + 1-tick retry for emx_timer)", "kernel_refs": [ "sys/dev/netif/emx/if_emx.c:1914", "sys/dev/netif/emx/if_emx.c:1928", "sys/dev/netif/emx/if_emx.c:2030", "sys/dev/netif/emx/if_emx.c:1534", "sys/dev/netif/emx/if_emx.c:4196", "sys/dev/netif/emx/if_emx.c:2026", "sys/dev/netif/emx/if_emx.c:1227", "sys/kern/kern_timeout.c:1091", "sys/kern/kern_timeout.c:920", "sys/kern/lwkt_serialize.c:234" ], "poc_changes": "No seed PoC. Authored emx_callout_replica.ko (out-of-tree kmodule replicating the emx callout protocol; adapted from DF-3715s em replica with emx line citations) plus emx_callout_replica_fixed.c (fix.diff shape) because the guest has no emx hardware; the deadlock is in the generic protocol emx uses, so the replica exercises the identical kernel paths. One replica iteration had a module-local bug (fx_c missing callout_init_mp -> KKASSERT panic in MY module); fixed and rerun.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64", "runtime_sec": 480, "guest_dirty": 0, "build_cmd": "cd /root/emxkmod && make (fixed-shape: /root/emxkmod3)", "run_cmd": "vm.sh run_root kldload /root/emxkmod/emx_callout_replica.ko; sysctl kern.emxreplica.arm=1; sysctl kern.emxreplica.stop_gc=1; (sysctl kern.emxreplica.stop_blk=1 &) # then vm.sh reset with-src", "code_hash": "7bb51ebf6949e731abd35dcd6dbe651378562ff034cb64d7fca24f31acf98004", "notes": "Triggers on emx hardware from ordinary privileged operations: ifconfig emx0 down/up, SIOCSIFMTU/SIOCSIFCAP on a running interface, detach/kldunload, suspend/resume, watchdog reinit, LSC interrupt - any path reaching emx_stop/emx_intr_body while emx_timer is INPROG-parked. Narrow per-call window (~1/s timer) but repeatable; impact is permanent system degradation (two wedged threads + CPU-wide callout starvation). Fixed-shape replica hash: 134c8fdfdf6c9d5defaf0d4e501ab5425ace17191a01b4678528109c9494db4e.", "recommended_fix": "Make emx_timer() non-blocking: lwkt_serialize_try() with a 1-tick callout_reset retry (emx_txgc_timer pattern).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Protocol-level validation on the guest: baseline (current emx_timer shape) = permanent AB-BA wedge (stopper D4 costp, softclock B0 slize); patched shape (exact fix.diff protocol: try + 1-tick retry + double-check under serializer) = the same emx_stop-shaped callout_stop completes, pending=0, module unloads cleanly. Full driver-kernel rebuild validation not possible without emx hardware.", "fix_evidence": "findings/poc/DF-3738/fix.diff; run.log fixed-shape validation section" } |