# DF-0702 — Mbuf UAF and double-free in `centre_test` and `send_state` (`sosend` consumes `m`, loop reuses it)

> Same ipfw3_sync dead-code caveat as DF-0700 — see that VERDICT for the full
> unreachability proof. Summary: the bug is real at source level; both functions are
> unreachable because the sockopt dispatcher (`ip_fw3_ctl_sync_ptr`, `ip_fw3.c:133`)
> is never assigned, and the one live-looking pointer (`ipfw_sync_send_state_prt`,
> assigned in MOD_LOAD) is never *called* by anyone.

## Verdict

**NOT REPRODUCED — LATENT dead code.** Real source bug (classic "sosend eats my
mbuf" UAF + double-free); unreachable on this kernel. Fix is defense-in-depth.

## The bug (source-level trace)

### `ip_fw3_ctl_sync_centre_test()` (`ip_fw3_sync.c:313-349`)

```c
m = m_getl(len, M_WAITOK, MT_DATA, M_PKTHDR, &nsize);   /* :330 — ONE mbuf built */
...
for (i = 0; i < fw3_sync_ctx.count; i++) {
    error = so_pru_sosend(fw3_sync_ctx.centre_socks[i],
            NULL, NULL, m, NULL, 0, td);                 /* :340 — passes SAME m */
    if (error) { ... return -1; }
}
m_free(m);                                                /* :347 — free again */
```

`so_pru_sosend` → `sosendudp` (`sys/kern/uipc_socket.c:960-1060`). On the success
path `sosendudp` **consumes the mbuf**: it either hands `top` to the lower layer
which frees it after TX, or — on the in-place `goto release` paths —
`m_freem(top)` at `uipc_socket.c:1058-1059`. In all cases `sosendudp` sets
`top = NULL` at `:1053` to mark the transfer of ownership
(`/* sent or freed in lower layer */`). So after iteration `i=0`, the caller's `m`
is **freed**. Iteration `i=1` passes the freed `m` to `sosendudp` → **use-after-free
read** (and the lower layer dereferences/frees it again). After the loop,
`m_free(m)` at `:347` frees it a **third time → double-free** (slab INVARIANTS trip,
or silent cross-type corruption on a noinv kernel).

### `ip_fw3_sync_send_state()` (`ip_fw3_sync.c:438-467`)

Same shape, no trailing `m_free`:

```c
m = m_getl(len, M_WAITOK, MT_DATA, M_PKTHDR, &nsize);   /* :446 */
...
for (i = 0; i < fw3_sync_ctx.count; i++) {
    error = so_pru_sosend(fw3_sync_ctx.centre_socks[i],
            NULL, NULL, m, NULL, 0, td);                 /* :459 — SAME m each iter */
    ...
}
return;                                                   /* :466 — no m_free */
```

Iteration `i≥1` reuses the freed `m` → **UAF** (read + lower-layer deref/free). No
double-free here (no trailing `m_free`), but the UAF is the same class.

### Why the loop even runs (the `count > 1` precondition)

Both loops are gated on `fw3_sync_ctx.count`, which is **only ever set** by
`ip_fw3_ctl_sync_centre_conf` (the DF-0700/0701 function). So the UAF needs `count ≥
2`, which needs `centre_conf` to have been called.

## Why unreachable on this kernel

Two independent layers of dead code:

1. **`centre_test`** is a sockopt handler → only reachable through
   `ip_fw3_ctl_sync_sockopt` → only dispatched via `ip_fw3_ctl_sync_ptr`
   (`ip_fw3.c:133`), which is **never assigned anywhere** (grep-verified). Dead.
   And `centre_conf` (needed to set `count`) is dead for the same reason — so
   `count` stays at its BSS zero and the loop body never executes even if
   `centre_test` were reached.

2. **`send_state`** is assigned to `ipfw_sync_send_state_prt` in MOD_LOAD
   (`ip_fw3_sync.c:474`), so it *looks* live — but
   `ipfw_sync_send_state_prt` is **never called anywhere in the tree** (grep shows
   only the declaration at `:85` and the assignment at `:474`; no call site). And
   even if it were called, `count` is 0 (needs dead `centre_conf`), so the loop is
   a no-op.

Plus the base-kernel ipfw3 stub returns `ENOPROTOOPT` until `kldload ipfw3`, and
the modules aren't loaded by default. Empirically confirmed by
`ipfw3_sync_deadpath` (`run.log`).

## Fix (defense-in-depth)

Pass a **per-iteration copy** of the mbuf to `so_pru_sosend` (each `sosend` then
owns+frees its own copy), and free the original exactly once after the loop. See
`fix.diff` (both functions):

```c
for (i = 0; i < fw3_sync_ctx.count; i++) {
    struct mbuf *mc = m_copym(m, 0, M_COPYALL, M_NOWAIT);   /* +DF-0702 */
    if (mc == NULL) { ...; m_free(m); return -1; }
    error = so_pru_sosend(fw3_sync_ctx.centre_socks[i],
            NULL, NULL, mc, NULL, 0, td);                   /* pass the copy */
    ...
}
m_free(m);   /* free the original once */
```

## Fix validation

- `fix.diff` applies cleanly (both hunks).
- Compile-validated with DF-0701/0703, `make -j6 nativekernel` → **rc=0**.
- `fix_status: not_testable` — dead code; validated applies + compiles.

## Files

| file | desc |
|---|---|
| `ipfw3_sync_deadpath.c` | empirical dead-path demo |
| `fix.diff`              | m_copym per iteration + single m_free (both functions) |
| `run.log`               | dead-path demo output (ENOPROTOOPT) |
| `env.txt`               | guest environment |

## Kernel references (confirmed)

- `sys/net/ipfw3_basic/ip_fw3_sync.c:313-349` — `centre_test` (UAF + double-free)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:330` — single `m_getl`
- `sys/net/ipfw3_basic/ip_fw3_sync.c:340-341` — `so_pru_sosend(..., m, ...)` in loop
- `sys/net/ipfw3_basic/ip_fw3_sync.c:347` — `m_free(m)` after loop (double-free)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:438-467` — `send_state` (UAF, no double-free)
- `sys/net/ipfw3_basic/ip_fw3_sync.c:446` — single `m_getl`
- `sys/net/ipfw3_basic/ip_fw3_sync.c:459-460` — `so_pru_sosend(..., m, ...)` in loop
- `sys/kern/uipc_socket.c:1044-1053` — `sosendudp` consumes `top` (`top = NULL`)
- `sys/net/ipfw3/ip_fw3.c:133` — `ip_fw3_ctl_sync_ptr = NULL` (dead dispatcher)
