DF-4025 / verdict.json
{ "finding_id": "DF-4025", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Mechanism PROVEN on the guest kernel by a deterministic replica of if_re.cs exact callout/serializer protocol (re(4) instance of DF-3715); re-specific runtime hardware-gated, inconclusive per family convention. re_attach arms one MP callout (callout_init_mp(&sc->re_timer), if_re.c:604; scheduled every hz by re_init :1881) whose callback re_tick BLOCKS on the driver serializer by hand (lwkt_serialize_enter, if_re.c:1358) and self re-arms UNDER it (re_tick_serialized :1371-1380). re_stop(sc,TRUE) issues callout_stop(&sc->re_timer) at if_re.c:2000 while HOLDING that serializer (every caller holds it: re_detach :909-911, re_suspend :2045-2047, re_shutdown :2116-2118, re_ioctl SIOCSIFFLAGS :1918, re_init :1871), clearing IFF_RUNNING only afterwards (:2001). DragonFly callout_stop is synchronous (kern_timeout.c:1091 -> _callout_cancel_or_stop sync=1; ssleep \"costp\" at :923), so a re_tick dequeued on another CPU and parked in lwkt_serialize_enter deadlocks against the stopper (AB-BA): both threads wedge permanently, if_serializer is never released (all later ioctls/if_start on reX block), and the affected CPUs softclock stops delivering ALL callouts. Guest replica (rereplica.ko, build.log BUILD_RC=0): arm -> re_tick-shape handler parks (fired=720 ran=719) -> re_stop-shape stop under the serializer -> dmesg ends at the AB-BA DEADLOCK IS PROVEN marker and NEVER prints the post-callout_stop line; stopper thread 1120 stuck D5/wchan costp across samples; the wedged guest could subsequently NOT be shut down cleanly (callout starvation) and was force-reset with-src. Triggers on re hardware: ifconfig reX down/up, SIOCSIFCAP reinit, detach, suspend, reboot -- racing the 1 Hz tick.", "exploit_chain": "On re(4) hardware: any re_stop(TRUE) (root-triggered ifconfig down/detach/suspend/reboot) racing the 1 Hz re_tick on another CPU -> permanent kernel deadlock of two threads + CPU-wide callout starvation; system degradation DoS, not memory corruption.", "evidence": "run.log (replica session transcript: dmesg marker sequence ending at AB-BA DEADLOCK IS PROVEN, ps 1120 D5 costp sysctl kern.rereplica.stop=1 across two samples, stop output never completing, plus the failed clean shutdown); build.log (module built on guest, BUILD_RC=0); rereplica.c/Makefile; env.txt (no re hardware, md5 match); fix.diff (non-blocking re_tick)", "kernel_refs": [ "sys/dev/netif/re/if_re.c:604", "sys/dev/netif/re/if_re.c:1354", "sys/dev/netif/re/if_re.c:1358", "sys/dev/netif/re/if_re.c:1371", "sys/dev/netif/re/if_re.c:1380", "sys/dev/netif/re/if_re.c:1871", "sys/dev/netif/re/if_re.c:1881", "sys/dev/netif/re/if_re.c:1918", "sys/dev/netif/re/if_re.c:1988", "sys/dev/netif/re/if_re.c:2000", "sys/dev/netif/re/if_re.c:2001", "sys/kern/kern_timeout.c:1091", "sys/kern/kern_timeout.c:923" ], "poc_changes": "No seed PoC. Authored rereplica.ko (out-of-tree kmodule) replicating res protocol: one MP callout whose handler does a BLOCKING lwkt_serialize_enter + under-lock conditional self re-arm (re_tick/re_tick_serialized shape), torn down by callout_stop under a held serializer in re_stops exact call order (stop then clear running). 1-tick arm period instead of hz to make the race window deterministic; real driver arms at hz.", "attempts": 3, "guest_uname": "DragonFly dfbsd 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": 300, "guest_dirty": 0, "build_cmd": "cd /root/rereplica && make (bsd.kmod.mk)", "run_cmd": "kldload /root/rereplica/rereplica.ko; sysctl kern.rereplica.arm=1; sleep 2; (sysctl kern.rereplica.stop=1 >/tmp/stop4025.out 2>&1 &); sleep 6; dmesg | grep rereplica | tail -4; ps -o pid,stat,wchan,command -ax | grep stop=1", "code_hash": "16620f8023070bcb89f5e39235277f83ae6fbde1336994e3975aa43c8c65a4fb", "notes": "Pass-2 checklist item (c) for if_re.c (DF-3715 family re-arm question). The wedged guest surviving vm.sh down cleanly is itself evidence: callout starvation prevented an orderly poweroff; force-killed and reverted to the with-src snapshot (guest_dirty=0 post-reset). re_tick_serializeds early-return gate (:1371) cannot save the parked callback because re_stop clears IFF_RUNNING only AFTER callout_stop returns -- which it never does.", "recommended_fix": "Make re_tick() non-blocking: lwkt_serialize_try() with a 1-tick callout_reset retry when the serializer is busy (see fix.diff; the same shape proposed for em_timer in DF-3715).", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (try-enter + 1-tick retry in re_tick) applies clean against the read-only tree; a runtime fix-validation would need re(4) hardware to race re_stop against a real re_tick. The replicas wedged protocol is exactly what the fix removes.", "fix_evidence": "findings/poc/DF-4025/fix.diff" } |