# DF-0474 — ipfw3 zero-length opcode infinite loop (DoS)

## Verdict
**REPRODUCED** — confirmed live via the `ip_fw3_unregister_module` path (kldunload hang) and the `ip_fw3_chk` path (outgoing packet hang). Fix validated.

## Mechanism
The inner opcode-iteration loop in both `ip_fw3_chk` (`sys/net/ipfw3/ip_fw3.c:493-495`) and `ip_fw3_unregister_module` (`sys/net/ipfw3/ip_fw3.c:201-204`) is:

```c
for (l = f->cmd_len, cmd = f->cmd; l > 0;
     l -= cmdlen,
     cmd = (ipfw_insn *)((uint32_t *)cmd + cmdlen)) {
    cmdlen = F_LEN(cmd);            /* = cmd->len & 0x3f */
    ...
}
```

`F_LEN(cmd) = cmd->len & 0x3f` (`ip_fw3.h:122`). If the low 6 bits of `cmd->len` are 0 (e.g. `len = 0x80` = `F_NOT|0`, or `0x40` = `F_OR|0`), `cmdlen == 0` and **neither `l` nor `cmd` advances**, so `l > 0` holds forever. The loop spins on the same `cmd`, wedging the calling thread.

## Live reproduction (unfixed #0 kernel)

### Path A — unregister hang (cleanest)
1. `sysctl net.filters_default_to_accept=1` (keep SSH alive).
2. `kldload ipfw3 + ipfw3_basic`.
3. Install a rule with `cmd[0].len = 0x80` (F_NOT | F_LEN=0), `cmd_len=2`.
4. `kldunload ipfw3_basic` → `ip_fw3_unregister_module` enters the inner loop at :201-204, `F_LEN(cmd)==0` → `cmdlen=0` → infinite spin. The kldunload(2) syscall never returns. `timeout 10` cannot kill it (the thread is stuck in kernel mode). The guest becomes unresponsive.

### Path B — packet-path hang
The same rule, when reached during packet processing by `ip_fw3_chk`, spins the inner loop at :493-495 on the calling netisr thread. Outgoing packets through `ip_output` → `PFIL_OUT` → `ip_fw3_check_out` → `ip_fw3_chk` hang. (Loopback `127.x` bypasses `PFIL_OUT` on DragonFly, so the trigger packet must use a non-loopback destination.)

## Evidence
- `run.log` — the decisive run: buggy rule installed, then the SSH session became unresponsive (outgoing packets hung in `ip_fw3_chk`). No panic (it's a pure infinite loop). `boot.log` shows no crash.

## Threat model
Root (or any process with raw-socket + `IP_FW_X` `setsockopt` privilege) installs a malformed rule. Any matching packet then wedges the netisr / calling thread on the affected CPU. On a single-CPU deployment this is a complete IP stack DoS; on multi-CPU it degrades one CPU at a time. This is a **DoS, not a privilege escalation** — the primitive is an infinite loop, not memory corruption.

## Fix
`fix.diff` — add `if (cmdlen == 0) break;` after `cmdlen = F_LEN(cmd);` in BOTH loops (chk at :495 and unregister at :204). Validated: built the fixed `ipfw3.ko`, loaded it, installed the buggy rule, sent a UDP packet — the packet went through cleanly (no hang), SSH stayed alive.

## Fix validation
- **Before (unfixed)**: outgoing packets after buggy-rule install hang in `ip_fw3_chk`; kldunload hangs in `ip_fw3_unregister_module`.
- **After (fixed ipfw3.ko)**: same buggy rule installed, `nc -u 10.0.2.2 9` returned `rc=0` (packet passed), SSH alive, `date` printed. The `if (cmdlen == 0) break;` causes `ip_fw3_chk` to skip the malformed rule immediately.
- (The `kldunload` validation path hit an **unrelated** `rn_flush` routing-table panic during module unload — not caused by this fix; the chk-path validation above is the clean before/after comparison.)

## Build / Run
```
cc -o df0474_install df0474_install.c    # installs the buggy rule
# then: sysctl net.filters_default_to_accept=1; kldload ipfw3; kldload ipfw3_basic
#       ./df0474_install
#       kldunload ipfw3_basic   # HANGS on unfixed kernel
```
