⬢ DragonFlyBSD Kernel Audit
DF-4046 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4046",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Mechanism PROVEN on the guest kernel by a deterministic replica of if_stge.cs exact callout/serializer protocol (stge(4) instance of DF-3715(em)/DF-4025(re)); stge-specific runtime hardware-gated, inconclusive per family convention. stge_attach arms one NON-MP callout (plain callout_init, if_stge.c:587; softclock dispatches it under the MPLOCK, kern_timeout.c:575-590) scheduled every hz by stge_init (:2059). Its callback stge_tick BLOCKS on the driver serializer by hand (lwkt_serialize_enter, :1758) and re-arms itself UNCONDITIONALLY under it (:1775 -- no IFF_RUNNING gate at all, unlike re). stge_stop() issues callout_stop(&sc->sc_tick_ch) at :2111 as its FIRST teardown action while HOLDING that serializer (every caller holds it: stge_detach :820-825, stge_shutdown :1049-1051, stge_suspend :1060-1063, stge_ioctl :1280, stge_init :1900/:1907 -- itself reachable from SIOCSIFMTU :1265, the watchdog :1238, and the interrupt paths force_init :1438-1440), clearing IFF_RUNNING only at the very end (:2166). DragonFly callout_stop is synchronous (kern_timeout.c:1091-1093 -> _callout_cancel_or_stop sync=1; ssleep costp at :920), so a stge_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 stgeX block), and the affected CPUs softclock stops delivering ALL callouts. Guest replica (stgereplica.ko, build.log BUILD_RC=0): arm -> stge_tick-shape handler parks (fired=202 ran=201) -> stge_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 43529 stuck D0/wchan costp while (softclock 2) sits blocked in wchan slize (lwkt_serialize_enter) -- the full AB-BA visible in one ps; the observing shell never completed its sleep and the wedged guest could NOT be shut down cleanly (vm.sh reset: guest still up after shutdown), force-killed and reverted to the with-src snapshot. Triggers on stge hardware: ifconfig stgeX down/up, SIOCSIFMTU/SIOCSIFCAP reinit, Tx-underrun/host-error force-init from the interrupt path, detach, suspend, reboot -- racing the 1 Hz tick.",
  "exploit_chain": "On stge(4) hardware: any stge_stop (root-triggered ifconfig down/detach/suspend/reboot, or the interrupt-path force-init) racing the 1 Hz stge_tick on another CPU -> permanent kernel deadlock of two threads + CPU-wide callout starvation; system-degradation DoS, not memory corruption.",
  "evidence": "run.log (wedge session transcript + post-wedge ps showing 43529 D0 costp vs (softclock 2) slize + serial-console marker sequence ending at AB-BA DEADLOCK IS PROVEN); build.log (BUILD_RC=0); stgereplica.c/Makefile; env.txt (no stge hardware); fix.diff (non-blocking stge_tick)",
  "kernel_refs": [
    "sys/dev/netif/stge/if_stge.c:587",
    "sys/dev/netif/stge/if_stge.c:1758",
    "sys/dev/netif/stge/if_stge.c:1775",
    "sys/dev/netif/stge/if_stge.c:1777",
    "sys/dev/netif/stge/if_stge.c:1900",
    "sys/dev/netif/stge/if_stge.c:1907",
    "sys/dev/netif/stge/if_stge.c:1280",
    "sys/dev/netif/stge/if_stge.c:1438",
    "sys/dev/netif/stge/if_stge.c:2098",
    "sys/dev/netif/stge/if_stge.c:2111",
    "sys/dev/netif/stge/if_stge.c:2166",
    "sys/kern/kern_timeout.c:1091",
    "sys/kern/kern_timeout.c:920"
  ],
  "poc_changes": "No seed PoC. Authored stgereplica.ko (out-of-tree kmodule) replicating stges protocol with stge-specific fidelity: plain NON-MP callout_init (MPLOCK dispatch), blocking lwkt_serialize_enter, UNCONDITIONAL under-lock self re-arm, and callout_stop as the first action of a serializer-held stop in stge_stops exact call order (IFF_RUNNING cleared last). 1-tick arm period instead of hz for a deterministic window; real driver arms at hz.",
  "attempts": 3,
  "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 /root/stgereplica && make",
  "run_cmd": "kldload /root/stgereplica/stgereplica.ko; sysctl kern.stgereplica.arm=1; sleep 2; (sysctl kern.stgereplica.stop=1 >/tmp/stop4046.out 2>&1 &); sleep 6; dmesg | grep stgereplica | tail -4; ps -o pid,stat,wchan,command -ax | grep stop=1",
  "code_hash": "5ae48e250907c35a9d4e7e25bd02e6f047419ead4f6ebcfa82849b3e66c62613",
  "notes": "Pass-2 checklist item (c) for if_stge.c. The wedged guest surviving the clean-shutdown attempt is itself evidence (callout starvation prevented orderly poweroff); force-killed and reverted to the with-src snapshot, guest healthy afterwards (guest_dirty=0 post-reset). stge_ticks unconditional re-arm (:1775) means even a hypothetical non-synchronous stop cannot prevent resurrection once the parked callback eventually runs -- the only complete fix is a never-blocking tick.",
  "recommended_fix": "Make stge_tick() non-blocking: lwkt_serialize_try() with a 1-tick callout_reset retry when the serializer is busy (see fix.diff; same shape as DF-3715(em)/DF-4025(re)).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (try-enter + 1-tick retry in stge_tick) applies clean against the read-only tree (git apply --check); a runtime fix-validation would need stge(4) hardware to race stge_stop against a real stge_tick. The replicas wedged protocol is exactly what the fix removes.",
  "fix_evidence": "findings/poc/DF-4046/fix.diff"
}