DF-3449 / verdict.json
{ "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" ] } |