⬢ DragonFlyBSD Kernel Audit
DF-3462 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3462",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "firewall-policy divergence: IP_FW_SET_TOGGLE flips only CPU0s per-CPU sets bitmap, so ipfw3 set disable/enable is silently ineffective for every flow hashed to CPUs 1..N-1 (deny/allow/count/skipto rules in the toggled set keep their old state on 5/6 of traffic on a 6-CPU box, while `ipfw3 set show` reports the operator-facing state as changed)",
  "confidence": "certain",
  "verdict": "Reproduced deterministically on the 6-CPU INVARIANTS guest: with rule 104 (`count udp dst-port 44990`) in set 3 and `ipfw3 set toggle 3` applied (`ipfw3 set show` => disable: 3), datagrams from bound source aliases 127.0.0.{4,8,13,19,20,24} to local 127.0.0.1 (flows hashing to CPU0 via toeplitz over the address pair, INP_MPORT_HASH_UDP) are skipped by the rule, while the other 17 source aliases still hit it (aggregated per-CPU sibling counters via IP_FW_GET). Toggling back restores uniformity, and the differential is reversible/repeatable across four phases (run.log). Root cause is certain from source: ip_fw3_ctl_set_toggle (ip_fw3_set.c:191-204) mutates only fw3_ctx[mycpuid] with mycpuid==0 forced by ASSERT_NETISR0 (raw_ip.c:308), while per-packet matching uses fw3_ctx[mycpuid] of the flows cpu (ip_fw3.c:350,487); every sibling set handler (move_set/move_rule/swap/flush) and ipfw2s ipfw_set_disable_dispatch (ip_fw2.c:5849-5875) broadcast via netisr_domsg - only toggle was missed. fix.diff adds the same broadcast (set_toggle_dispatch + netisr_domsg); with the rebuilt ipfw3.ko the DISABLED phases show delta 0 for ALL sources (uniform skip) and ENABLED phases are unchanged (run.patched.log) - divergence eliminated.",
  "exploit_chain": "Operator scenario: incident responder runs `ipfw3 set disable N` (toggle) to stop rules in set N (e.g. a misfiring deny set, or to lift a quarantine set) - `ipfw3 set show` confirms disabled, but the sets deny/allow rules remain enforced for all flows whose toeplitz address-hash lands on CPUs 1..N-1 (deterministic per address pair; ~5/6 of flows here). Conversely there is no way to disable a set for those flows at all. Not an unpriv->root chain: the trigger is the root-only IP_FW_SET_TOGGLE sockopt; the victim is the firewall policy itself.",
  "evidence": "run.log (baseline, decisive 4-phase run: DISABLED phases show delta 0 for {4,8,13,19,20,24} and delta 2 for 17 others); run.patched.log (same script, fix.diff module: DISABLED => all delta 0); VERDICT.md (mechanism + source cites); srcscan.sh/udp1shot.c (instrument); build.log (module rebuild); panic.teardown.txt (unrelated stock kldunload ipfw3 rn_flush crash, side lead)",
  "kernel_refs": [
    "sys/net/ipfw3/ip_fw3_set.c:191-204",
    "sys/net/ipfw3/ip_fw3_set.c:206-215",
    "sys/net/inet/raw_ip.c:308",
    "sys/net/ipfw3/ip_fw3.c:350",
    "sys/net/ipfw3/ip_fw3.c:485-489",
    "sys/net/ipfw3/ip_fw3.c:1020-1027",
    "sys/netinet/ip_demux.c:71-80",
    "sys/netinet/udp_usrreq.c:1137-1147",
    "sys/net/ipfw/ip_fw2.c:5849-5875"
  ],
  "poc_changes": "no seed: finding + instrument authored in this run (pass 2). Key instrument learnings: the flow cpu is chosen by udp_sends 4-tuple-address redispatch (udp_usrreq.c:1137-1147), not by the sending processs cpu, so flows are steered via bound 127/8 source aliases against a fixed local dst; IP_FW_GET aggregates per-cpu sibling counters so one counter delta per flow is the cleanest per-cpu enforcement observable; `ipfw3 -S show` is required to read rules inside a disabled set (CLI hides them otherwise, sbin/ipfw3/ipfw3.c:751-754).",
  "attempts": 12,
  "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": "cd /usr/src && patch -p1 < /root/fix3462.diff && cd sys/net/ipfw3 && make && make install   # module-only; see build.sh + build.log",
  "run_cmd": "sh /root/run3462.sh   # guest, root; prereqs in README.md (kldload ipfw3+basic+layer4, lo0 aliases, rule 104)",
  "code_hash": "ccc0332c47074fa975a0b436b686dde82841ce56825b48c36b55aa798806270a",
  "notes": "Verification-only caveat: deny-direction packet-delivery demo was confounded by DFly per-cpu UDP wildcard-listener placement (`netstat -s -p udp` dropped due to no socket) and is excluded from the claim; the count-rule skip uses the identical per-cpu skip branch (ip_fw3.c:487-489) that governs deny/allow. Side observation retained for maintainers: one `kldunload ipfw3` (stock fini path, no rules beyond 104/105) panicked in rn_flush (panic.teardown.txt) - not on the toggle path, independent lead. Guest was left clean: modules+aliases reverted by final `vm.sh reset with-src` (all baseline and patched measurements were taken between resets; the patched run loaded the fixed module fresh from a clean boot without any unload).",
  "recommended_fix": "Broadcast the toggle to all CPUs like the sibling handlers: add set_toggle_dispatch() that XORs fw3_ctx[mycpuid]->sets on each cpu via netisr_forwardmsg_all and call it from ip_fw3_ctl_set_toggle through netisr_domsg (fix.diff, validated).",
  "fix_status": "fixed",
  "fix_kernel_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 (same kernel; patched ipfw3.ko module rebuilt from the same tree and loaded from a clean vm.sh reset with-src boot)",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to /usr/src in-guest, ipfw3.ko module rebuilt+installed (build.log), loaded fresh after vm.sh reset with-src. The exact decisive script re-run: DISABLED phases now yield delta 0 for ALL 23 source aliases (uniform skip across the 6 per-cpu contexts) and ENABLED phases are identical to baseline (all delta 2) - the per-flow divergence signature ({4,8,13,19,20,24}->0 vs others->2 in DISABLED state) is gone. Baseline signature preserved in run.log.",
  "fix_evidence": "run.patched.log (patched: DISABLED => all-zero deltas, both disabled phases); run.log (baseline divergence); build.log (ipfw3.ko rebuild); fix.diff"
}