# DF-0569 — Heap OOB write via byte-swapped `alias_port` used as array index

**Severity:** High  
**Class:** CWE-787 Out-of-bounds Write  
**File:** `sys/net/ipfw3_nat/ip_fw3_nat.c`  
**Kernel:** DragonFly 6.5-DEVELOPMENT #0 (master DEV, x86-64)

## Verdict: REPRODUCED + FIX VALIDATED

The bug is **real and confirmed** by two independent lines of evidence:

1. **Deterministic arithmetic proof** — 1008 out of 64511 possible host-order alias port values (1.56%) produce out-of-bounds negative array indices `[-1020, -1]`, writing an 8-byte pointer up to 8160 bytes before `tcp_in[0]`.

2. **Runtime state-table evidence** — Driving 5000 UDP flows through ipfw3 NAT produced **102 TCP entries** in the NAT state table (impossible with UDP-only traffic — these are UDP `s2` structs misplaced into `tcp_in[]` by the byte-swap OOB). Every OOB entry's host-order port has low byte exactly 0–3.

## Bug mechanism

In `pick_alias_port()` (line 439), the alias port is stored in **network byte order**:
```c
s->alias_port = htons(krandom() % ALIAS_RANGE + ALIAS_BEGIN);
```

At lines 423/425 (same-CPU) and 721/723 (cross-CPU), the network-order value is used **directly as an array index** without `ntohs()`:
```c
alias->tcp_in[s->alias_port - ALIAS_BEGIN] = s2;  // line 423
alias->udp_in[s->alias_port - ALIAS_BEGIN] = s2;  // line 425
```

On little-endian x86-64, `htons()` byte-swaps the value. When the low byte of the host-order port is 0–3, the swapped value is < 1024, and `swapped_value - ALIAS_BEGIN` becomes negative (index range `[-1020, -1]`).

The read side (lines 204/209) uses the same byte-swapped index, so the connection "appears to work" while silently corrupting heap.

## Byte-swap arithmetic table

| host_val | htons result | index = result − 1024 | OOB? |
|----------|-------------|----------------------|------|
| 0x0400 (1024) | 0x0004 (4) | −1020 | YES |
| 0x0401 (1025) | 0x0104 (260) | −764 | YES |
| 0x0402 (1026) | 0x0204 (516) | −508 | YES |
| 0x0403 (1027) | 0x0304 (772) | −252 | YES |
| 0x0500 (1280) | 0x0005 (5) | −1019 | YES |
| 0x0800 (2048) | 0x0008 (8) | −1016 | YES |
| 0x0404 (1028) | 0x0404 (1028) | 4 | no |

**Probability:** 1008/64511 = **1.5625%** per new NAT'd connection.

## Reproduction

### Prerequisites
- Root access on DragonFlyBSD guest with ipfw3 modules available
- `kldload ipfw3 && kldload ipfw3_basic && kldload ipfw3_nat`

### Steps (automated by `run.sh`)
1. Set `net.filters_default_to_accept=1` (prevent SSH lockout)
2. Load ipfw3 modules
3. Configure NAT: `ipfw3 nat 1 config ip <vtnet0_ip>`
4. Add NAT rule: `ipfw3 add 100 nat 1 udp from any to any out`
5. Drive 5000+ UDP flows: `./nat_oob_trigger 5000 10.0.2.2`
6. Check state table: `ipfw3 nat 1 show state | grep " tcp " | wc -l`
   - **Bug present:** >0 TCP entries (OOB writes)
   - **Fixed:** 0 TCP entries

### Build
```sh
./build.sh
```

### Run
```sh
./run.sh 5000
```

## Fix

Add `ntohs()` at every array index site where the network-byte-order `alias_port` is used as an index (8 sites total: 3 read, 5 write). See `fix.diff`.

**Before/after comparison (5000 UDP flows):**
| Metric | Unpatched (#0) | Patched module |
|--------|---------------|----------------|
| TCP entries in state table | **102** | **0** |
| OOB writes | ~102 | 0 |
| All OOB entries low byte 0–3 | ✓ | N/A |

## Files

- `alias_port_oob_proof.c` — standalone arithmetic proof
- `nat_oob_trigger.c` — UDP flow driver (primary trigger)
- `nat_oob_trigger_tcp.c` — TCP connection driver (slow, for reference)
- `raw_syn_trigger.c` — raw SYN packet sender (fast TCP path)
- `run.sh` — automated setup + driver
- `build.sh` — build script
- `fix.diff` — git-apply-able fix (ntohs at all 8 index sites)
- `VERDICT.md` — detailed analysis
- `panic.txt` — kernel panic from raw SYN (separate checksum bug)
- `run_state_table.txt` — full state table (unpatched, 102 TCP entries)
- `fix_run_state_table.txt` — full state table (patched, 0 TCP entries)
