# REFUTATION RECORD (kept for DF-0748 triage) — candidate "ifnet_detach_event
# double-free in ip_fw3_log.c" — investigated as reserved DF-3465, refuted.

## Candidate (pass-2 hypothesis)

`ip_fw3_log_clone_destroy()` (sys/net/ipfw3_basic/ip_fw3_log.c:182-206) is
registered as a handler for the GLOBAL `ifnet_detach_event`
(ip_fw3_log.c:222-225) with the same signature as the cloner's
`ifc_destroy`.  Hypothesis: any interface detach/rename with `if_dunit`
colliding with a live `ipfwN` clone unit would type-confuse the victim ifnet
into `bpfdetach()/if_detach()/if_free()` — recursive double detach + double
free (and via SIOCSIFNAME, if.c:2206, a mid-rename full detach+free).

## Why it is NOT reachable in this tree (two independent blockers)

### Blocker 1 — the whole ipfw3 log-if subsystem is dead code

* `ip_fw3_log_modevent()` (ip_fw3_log.c:213) — the only function that
  attaches the "ipfw" cloner and registers the event handler — has **no
  caller anywhere in the tree**.  `ipfw3_basic_modevent()`
  (sys/net/ipfw3_basic/ip_fw3_basic.c:644-660) wires only
  `ip_fw3_state_modevent` and `ip_fw3_table_modevent`, NOT the log one.
* `ip_fw3_log_ptr` (sys/net/ipfw3/ip_fw3.c:134) is initialized to NULL and
  never assigned; `check_accept`/`check_deny` (ip_fw3.c:252,263) guard on it
  and therefore never call `ip_fw3_log()` — so DF-0748's OOB table index is
  equally unreachable in-tree.
  Consequence: `log_if_lock` is never `lockinit`'d and `log_if_table[]`
  stays all-NULL.  (Filed separately as an Info/Low dormancy finding.)

### Blocker 2 — even when wired, the handler is a guaranteed silent no-op

`ifnet_detach_event_handler_t` is `void (*)(void *, struct ifnet *)`
(sys/net/if_var.h:724) and `EVENTHANDLER_INVOKE` calls
`eh_func(ee_arg, args...)` — **ctx FIRST, payload second**
(sys/sys/eventhandler.h:123).  `ip_fw3_log_clone_destroy` is
`int (*)(struct ifnet *)`, so when invoked as an event handler:

* its `ifp` parameter actually receives the registered context
  `&ipfw3_log_cloner` — a *static `struct if_clone`*, not an ifnet;
* the REAL detaching ifnet arrives as an ignored second argument.

Measured on the audit guest (kernel X86_64_GENERIC #0, module base
0xffffffff8264a000, `ipfw3_log_cloner` @ +0xc0 from the .ko symtab):

```
offsetof(struct ifnet, if_dunit) = 56
sizeof(struct if_clone)          = 64   (ifc_name@16, ifc_minifs@24, ifc_maxunit@28)
struct if_clone field @56        = low 4 bytes of ifc_destroy
                                   = low32(&ip_fw3_log_clone_destroy) ~ 0x8264xxxx
```

So the fake `if_dunit` is the low half of a kernel text pointer (>=
0x80000000 on every x86_64/i386 DFly link), which is always >= LOG_IF_MAX
(10): the handler returns EINVAL on every invocation.  Empirically confirmed:
with the subsystem force-wired by `wireshim.ko` (below), destroying/renaming
interfaces fires the handler (my tracer saw every event) and nothing
happened; `ipfw0` create/destroy via the cloner kept working.

## Artifacts here

* `wireshim.c` — KLD that performs exactly the missing one-line wiring
  (`ip_fw3_log_modevent(MOD_LOAD)`) plus a correctly-signed tracer handler.
  Used to empirically demonstrate blockers 1+2 on the guest.
* `run.sh` — the trigger sequence that was tried (destroy + rename paths).
* Guest observations (dmesg/serial) are quoted in this file; the double-free
  never manifested.

## Incidental guest observation (different file, NOT this candidate)

While unloading the wired stack, the guest panicked in `rn_flush` — this is
`ip_fw3_table_fini_dispatch()` (sys/net/ipfw3_basic/ip_fw3_table.c:576-587)
calling `rn_flush(table_ctx->node, ...)` for every table id although
`table_ctx->node` is only `rn_inithead`'d when a table is *created*
(ip_fw3_table.c:96-107).  `kldunload ipfw3_basic` with zero configured
tables NULL-derefs (`rn_flush(NULL)` -> read of 0x28).  Root-triggerable
local DoS, out of this batch's file scope — reported to the orchestrator in
the JSON `notes` for a dedicated audit.
