⬢ DragonFlyBSD Kernel Audit
DF-3311 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3311",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "dos",
  "confidence": "likely",
  "verdict": "Code defect certain: wg_down() cancels/drains the handshake and encrypt/decrypt tasks and purges their queues (if_wg.c:2763-2786) while the UDP sockets are still open (closed only at :2795), so an unprivileged peers packet driving wg_upcall()->wg_input() can enqueue a handshake packet and re-enqueue sc_handshake_task (if_wg.c:2166-2170; WG_PKT_IS_* checks need no valid crypto) AFTER the cancel; wg_clone_destroy() then kfrees the task arrays (:2883-2884), lockuninits the queues (:2885-2887) and kfrees the softc (:2917) that the late tasks callback argument points to. Dynamically: the FIRST race run (root create/up/destroy churn + nobody-spammed 148/92/64-byte packets on lo0) hard-wedged the guest within 45 s (ssh dead, vm down, QEMU alive, no panic text, no /var/crash dump - silent hang needing force-kill); 3 clean controls and 4 subsequent race reruns (~8000 iterations incl. a 12-peer widened variant) stayed up, so the wedge could not be repeated and full reproduced is not honestly claimable. Realistic ceiling: fatal local DoS raced between unprivileged packet input and a privileged config op; no path to uid=0 pursued (write content not attacker-chosen, window is teardown-only).",
  "exploit_chain": "",
  "evidence": [
    "findings/poc/DF-3311/VERDICT.md (interleaving diagram + 8-run campaign table)",
    "findings/poc/DF-3311/run.log (the wedge run #1: status down, boot.log silent, /var/crash empty)",
    "findings/poc/DF-3311/run.2.log run.3.log (controls clean; reruns 1 wedge / 5 races)",
    "findings/poc/DF-3311/fix.diff (close sockets before cancelling tasks in wg_down)"
  ],
  "kernel_refs": [
    "sys/net/wg/if_wg.c:2163",
    "sys/net/wg/if_wg.c:2166",
    "sys/net/wg/if_wg.c:2170",
    "sys/net/wg/if_wg.c:2189",
    "sys/net/wg/if_wg.c:2763",
    "sys/net/wg/if_wg.c:2795",
    "sys/net/wg/if_wg.c:2883",
    "sys/net/wg/if_wg.c:2885",
    "sys/net/wg/if_wg.c:2917"
  ],
  "poc_changes": "No wg(8) on guest: wgtool.c issues SIOCSWG directly (byte-exact struct layout from if_wg.h); spam.c sends type-1/2/3 packets whose type word + exact length (148/92/64B) pass WG_PKT_IS_* with zero valid crypto - the enqueue path never validates MACs before queueing, which is what makes the unprivileged side of this race possible.",
  "attempts": 5,
  "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": 2400,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o wgtool wgtool.c && cc -O2 -o spam spam.c",
  "run_cmd": "su -m nobody -c ./spam 127.0.0.1 51820 0 tag & ; sh raceA.sh 3000",
  "code_hash": "5d0bd6ccf1bd41c2cc575ff036deb71aabb2bac3c0c04565acdd9f14d96c7905",
  "notes": "One silent hard wedge in 5 race runs vs 3 clean controls; no crash dump available (wedge, not panic). Peer-lifetime half of teardown is known DF-0315; this finding covers the softc/task-queue half. Guest left clean (vm.sh reset with-src).",
  "recommended_fix": "In wg_down(), call wg_socket_uninit() FIRST (before any taskqueue_cancel/purge) so no new wg_input() can re-enqueue tasks/queues being torn down; a complete fix additionally needs a softc reference across wg_input() (see fix.diff).",
  "fix_status": "not_testable",
  "fix_kernel_uname": "",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Wedge occurred only in run #1 and never recurred on the unpatched kernel afterwards, so a baseline/patched A/B could not be driven; fix is by-inspection (reorders wg_socket_uninit() ahead of all cancels/purges).",
  "fix_evidence": "findings/poc/DF-3311/fix.diff"
}