⬢ DragonFlyBSD Kernel Audit
DF-3449 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3449",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "Deterministic panic reproduced on the stock INVARIANTS guest: kldunload dummynet3 succeeds while an ipfw3 `pipe` rule references module 2 because dummynet3s MOD_UNLOAD never calls ip_fw3_unregister_module(), leaving filter_funcs[2][O_DUMMYNET_PIPE] pointing at the unmapped module text; the next packet through ipfw3 executes the stale pointer (RIP == former dummynet3.ko load address 0xffffffff8260d000, execute page-fault, Idle/netisr context). Root-gated setup, unprivilege-gated trigger packet; panic-grade DoS, text-UAF primitive. fix.diff (add unregister+EBUSY, propagate error) validated in-guest: unload refused while rule exists, clean unload after rule deletion, no panic, post-unload pipe-rule compilation rejected.",
 "exploit_chain": "root: kldload ipfw3(+basic pinned) + dummynet3; ipfw3 add 100 pipe 1 ...; kldunload dummynet3 (succeeds - the bug); any unpriv traffic (ping) -> ip_fw3.c:506 check_body calls stale filter_funcs[2][0] == unmapped check_pipe -> Fatal trap 12, RIP = former module base. Escalation ceiling: if another KLD is loaded into the freed VA before the trigger packet, the indirect call lands in attacker-chosen module code with an ipfw3 calling convention - chaotic control-flow hijack, not developed further because setup already requires root (kldunload): impact honestly capped at panic/DoS.",
 "evidence": [
  "panic.txt: fault virtual address = 0xffffffff8260d000, instruction pointer = 0x8:0xffffffff8260d000, supervisor read instruction page not present, current process = Idle",
  "run.patched.log: kldstat line 6 1 0xffffffff8260d000 4000 dummynet3.ko proving RIP == former module base",
  "run.patched.log: baseline-free fixed run - UNLOAD_WITH_RULE_RC=1 (Device busy), UNLOAD_AFTER_DELETE_RC=0, PING_AFTER_UNLOAD_OK, ADDRULE_AFTER_UNLOAD_RC=64, FIX_VALIDATED, guest alive",
  "console_full.txt / run.patched.log console section: load/unload kprintf trail",
  "fix.diff: ip_fw3_unregister_module(MODULE_DUMMYNET_ID) + EBUSY propagation"
 ],
 "kernel_refs": [
  "sys/net/dummynet3/ip_dummynet3.c:1995",
  "sys/net/dummynet3/ip_dummynet3.c:2013",
  "sys/net/ipfw3/ip_fw3.c:505",
  "sys/net/ipfw3/ip_fw3.c:189",
  "sys/net/ipfw3_basic/ip_fw3_basic.c:640",
  "sys/net/dummynet3/ip_dummynet3.c:2067"
 ],
 "poc_changes": "Seed sketch had never been run: rewrote as a detached guest-side script (panic kills ssh mid-run); added net.filters_default_to_accept=1 (default-deny otherwise cuts ssh at kldload); added standalone pre-load of ipfw3_basic.ko because dummynet3s MODULE_DEPEND cascade otherwise unloads basic first and trips the known DF-0669 rn_flush(NULL) panic before our trigger can fire; configured pipe 1 before adding the rule so pre-unload sanity traffic flows.",
 "attempts": 4,
 "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": 1250,
 "guest_dirty": 1,
 "build_cmd": "for d in ipfw3 ipfw3_basic dummynet3; do (cd /usr/src/sys/net/$d && make obj && make); done",
 "run_cmd": "nohup sh /root/df3449_trigger.sh >/dev/null 2>&1 &  # then vm.sh log",
 "code_hash": "0658d2a13564f4fa5e02c8c168c096eb83ac90b45072e8abb16bd3757f81f29f",
 "notes": "Reserve-status note: naive kldunload dummynet3 (basic not pinned) instead panics in the dependency cascade at ip_fw3_table.c:578 rn_flush(NULL) - that is known DF-0669, not re-reported. The audited file ip_dummynet3_glue.c is dead code (not in sys/conf/files); finding filed against it as the ipfw3-hook side of the dummynet3 interface per task framing, defect located in ip_dummynet3.c. Guest left up after fix validation (patched source in /usr/src, rebuilt KLDs); orchestrator should reset before next spawn.",
 "recommended_fix": "Call ip_fw3_unregister_module(MODULE_DUMMYNET_ID) at the head of ip_dn_stop_dispatch(), replying EBUSY while ipfw3 rules still reference the module, and propagate the error through ip_dn_stop()/dummynet_modevent() so the linker keeps the module resident (mirror ipfw3_basic/nat/layer2/layer4).",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (KLD rebuilt from patched /usr/src, module-level fix)",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Baseline (unpatched dummynet3.ko): kldunload succeeds with live pipe rule; first ping panics at RIP = former module text base. Patched (fix.diff applied in /usr/src, module rebuilt): unload refused with EBUSY while rule 100 exists (traffic keeps flowing through the pipe); after ipfw3 delete 100 the unload completes cleanly, post-unload ping works with no panic, and compiling a new pipe rule is rejected because the module registration is gone. Guest alive at end of run.",
 "fix_evidence": [
  "run.patched.log (full untrimmed): UNLOAD_WITH_RULE_RC=1 / kldunload: cant unload file: Device busy; UNLOAD_AFTER_DELETE_RC=0; PING_AFTER_UNLOAD_OK; ADDRULE_AFTER_UNLOAD_RC=64; FIX_VALIDATED",
  "panic.txt: baseline Fatal trap 12 at 0xffffffff8260d000",
  "fix.diff"
 ]
}