# DF-0475 — ipfw3 act_ofs never validated → OOB pointer deref

## Verdict
**REPRODUCED** — confirmed live via OOB rule acceptance (missing validation) AND OOB kernel panic via the CHK_STATE path. Fix validated.

## Mechanism
`add_rule_dispatch` (`sys/net/ipfw3/ip_fw3.c:651`) copies `rule->act_ofs = ioc_rule->act_ofs;` verbatim from user input with **no check** that `act_ofs <= cmd_len`. `ACTION_PTR(rule)` (`ip_fw3.h:134-135`) computes `(ipfw_insn *)((uint32_t *)rule->cmd + rule->act_ofs)`, so when `act_ofs >= cmd_len` it points **past the `cmd[]` array** into adjacent kernel heap.

The OOB pointer is dereferenced in three places:
1. **`ip_fw3_chk` CHK_STATE case** (`ip_fw3.c:520-524`): `cmd = ACTION_PTR(f); l = f->cmd_len - f->act_ofs; goto check_body;` — reads OOB `cmd->module`/`cmd->opcode` and calls `filter_funcs[OOB_module][OOB_opcode]` (line 506) → **arbitrary/NULL function-pointer call**.
2. `lookup_next_rule` (`ip_fw3.c:294-296`) — on dummynet reinject.
3. `ip_fw3_dummynet_io` (`ip_fw3.c:608-611`) — on dummynet pipe delivery.

## Live reproduction (unfixed #0 kernel)
1. **Missing validation confirmed**: `setsockopt(IP_FW_X, IP_FW_ADD)` with `act_ofs=99, cmd_len=1` succeeds — the rule is installed. On a FIXED kernel this is rejected.
2. **OOB panic via CHK_STATE**: install a keep_state rule (creates states with `->stub` pointing at the rule) with OOB `act_ofs=99`, flood UDP to create states on all CPUs, install a check_state rule, flood again. When `check_check_state` finds a state, it returns `IP_FW_CTL_CHK_STATE`; `ip_fw3_chk` at :520-524 does `cmd = ACTION_PTR(f)` = `cmd + 99` = OOB heap, reads garbage `module`/`opcode`, calls `filter_funcs[garbage]` → **kernel panic**:
   ```
   Fatal trap 9: general protection fault while in kernel mode
   kernel: type 9 trap, code=0
   Stopped at ip_fw3_chk+0x1a4: ret
   ```
   (`dfbsd-qemu/boot.log`)

## Evidence
- `df0475_oob.c` — installs the OOB rule (`act_ofs=99, cmd_len=1`); prints "[+] installed buggy rule 200 (act_ofs=99, cmd_len=1) -- OOB accepted".
- `df0475_trigger.c` — the full state-path trigger (keep_state + check_state + flood) that causes the panic.
- `panic.txt` — the `Fatal trap 9 ... Stopped at ip_fw3_chk+0x1a4: ret` excerpt.

## Threat model
Root installs a crafted rule. Any subsequent traffic that triggers CHK_STATE (or dummynet reinject) dereferences the OOB pointer. On default GENERIC (INVARIANTS ON) this panics; on a kernel without INVARIANTS, the OOB function-pointer call could potentially be shaped via heap grooming into an arbitrary call (escalation primitive — not pursued here as it requires bypassing INVARIANTS).

## Fix
`fix.diff` — add `if (ioc_rule->act_ofs > ioc_rule->cmd_len) { kprintf(...); netisr_forwardmsg_all(...); return; }` in `add_rule_dispatch`. Validated: built fixed `ipfw3.ko`, loaded it, ran `df0475_oob` — dmesg shows `"ipfw3: refusing rule 200: act_ofs 99 > cmd_len 1"` (printed once per CPU); the rule is refused.

## Fix validation
- **Before**: `act_ofs=99, cmd_len=1` rule accepted; OOB panic via state path.
- **After**: `"ipfw3: refusing rule 200: act_ofs 99 > cmd_len 1"` (×6 CPUs); rule NOT installed; no OOB possible.

## Build / Run
```
cc -o df0475_oob df0475_oob.c         # proves missing validation (rule accepted)
cc -o df0475_trigger df0475_trigger.c # full OOB panic trigger via state path
# sysctl net.filters_default_to_accept=1; kldload ipfw3; kldload ipfw3_basic
# ./df0475_oob        # UNFIXED: "OOB accepted"; FIXED: rule refused
# ./df0475_trigger     # UNFIXED: kernel panic (trap 9 at ip_fw3_chk+0x1a4)
```
