# DF-0740 — gre_input2 missing packet length validation

## Verdict: REPRODUCED + FIX VALIDATED

**Bug:** `gre_input2()` in `sys/netinet/ip_gre.c` adjusts `m->m_data`, `m->m_len`,
and `m->m_pkthdr.len` by `hlen` (accumulated from GRE flags) **without checking
that `hlen <= m_pkthdr.len`**. A packet claiming CP|KP|SP option fields (+12 bytes)
but shorter than `hlen` causes an integer underflow in `m_len`/`m_pkthdr.len` and
an out-of-bounds `m_data` pointer.

**Root cause:** `sys/netinet/ip_gre.c:180-182` — no bounds check before:
```c
m->m_data += hlen;
m->m_len -= hlen;
m->m_pkthdr.len -= hlen;
```

**Impact:** Remote unauthenticated single-packet DoS (kernel panic). Requires a
configured GRE tunnel on the victim. The corrupted mbuf is re-enqueued via
`netisr_queue(NETISR_IP, m)` and processed by `ip_hashfn()`, which dereferences
the OOB `m_data` pointer → page fault → panic.

## Crash mechanism (detailed)

1. **Trigger:** A GRE packet with flags `CP|KP|SP` (0xB000) but a body shorter
   than the claimed option fields. Example: 24-byte packet (20 IP + 4 GRE header,
   zero option bytes), but `hlen` accumulates to 36 (20+4+4+4+4).

2. **Underflow:** `m_len -= 36` on a 24-byte mbuf → `m_len = -12`.
   `m_pkthdr.len -= 36` → `m_pkthdr.len = -12`.
   `m_data += 36` → points 12 bytes past the actual data.

3. **Stale M_LENCHECKED:** `gre_input2` clears `M_HASH` but NOT `M_LENCHECKED`.
   The original packet was validated by `ip_lengthcheck` (which set
   `M_LENCHECKED`). After gre strips the header, the flag persists, so
   `ip_hashfn` skips `ip_lengthcheck` for the corrupted mbuf.

4. **Signed/unsigned bypass:** `ip_input.c:463` checks `m->m_len < sizeof(struct ip)`.
   `sizeof` returns `size_t` (unsigned). With `m_len = -12` (signed), the
   implicit conversion makes `(-12)` → `~4 billion`, which is NOT `< 20`. The
   check passes, and the corrupted mbuf reaches the KASSERT zone.

5. **Page fault:** `ip_hashfn` reads the IP header at the OOB `m_data`. If the
   data happens to look like a valid IP header (or if `m_data` crosses a page
   boundary), the dereference hits unmapped memory → fatal trap 12 → panic.

6. **Reliable crash via heap spray:** Sending valid GRE packets first fills the
   mbuf pool with known data (0x45 at byte offset 36). The subsequent malformed
   packet may reuse a sprayed mbuf, making `ip_v = 4` at the OOB offset. This
   passes the version check and reaches the KASSERTs, or the OOB pointer crosses
   a page boundary.

## Crash signature (from serial console)

```
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address    = 0xfffff80118500000
fault code               = supervisor read data, page not present
instruction pointer      = 0x8:0xffffffff807b260b
current process          = Idle
Stopped at      ip_hashfn+0x19b:        movzwl  (%rax),%edi
db>
```

Also seen:
```
panic: vm_fault: fault on stack guard, addr: 0xfffff80117680000
ip_hashfn() at ip_hashfn+0x19b 0xffffffff807b260b
```

## GRE is a kernel module (not compiled into kernel)

**Important discovery:** GRE is NOT compiled into the `X86_64_GENERIC` kernel.
It's an auto-loaded module: `/boot/kernel/if_gre.ko`. When `ifconfig gre create`
runs, the kernel auto-loads `if_gre.ko` via the `if_clone` mechanism. The fix
must be applied to the **module**, not just the kernel source.

## Exploit chain

- **Bucket:** N/A (network packet injection, not heap corruption)
- **Primitive:** mbuf length underflow + OOB m_data pointer dereference
- **Conversion:** Direct page fault in `ip_hashfn` from the corrupted pointer
- **Outcome:** Kernel panic (DoS). No write primitive — the crash happens before
  any controlled write lands. No escalation to `uid=0` possible from this bug.
- **Chain file:** `exploit.c` (spray + trigger)

## Fix

Two-part fix in `sys/netinet/ip_gre.c`:

1. **Length check** before the mbuf adjustments (line 180):
```c
if (hlen > m->m_pkthdr.len) {
    m_freem(m);
    return (1);
}
```

2. **Clear M_LENCHECKED** alongside M_HASH (line 196):
```c
m->m_flags &= ~(M_HASH | M_LENCHECKED);
```

The second part is necessary because the stale `M_LENCHECKED` flag allows
`ip_hashfn` to skip `ip_lengthcheck` for the stripped inner packet, which can
also cause OOB reads from short inner data (e.g., TCP port read past the
packet boundary).

## Fix validation

| Test | Kernel/Module | Result |
|------|---------------|--------|
| Baseline (unpatched) | `#0` + stock `if_gre.ko` | **PANIC** at `ip_hashfn+0x19b` |
| Fixed module | `#0` + patched `if_gre.ko` | **No panic** — 200 exploit rounds survived |

The fix closes both crash paths: the length check drops malformed packets, and
the M_LENCHECKED clearing forces re-validation of the inner packet.

## How to reproduce

```sh
# Build
cc -O2 -o exploit exploit.c

# Setup GRE tunnel (as root — simulates admin configuration)
ifconfig gre0 create
ifconfig gre0 tunnel 127.0.0.1 127.0.0.2
ifconfig gre0 inet 172.16.0.1 172.16.0.2 netmask 0xffffffff up

# Run exploit (as root — needs raw socket)
./exploit 100
# Expected on unpatched kernel: panic within ~20-100 rounds
# Expected on patched module: survives all rounds
```
