# DF-0742 — gre_input2 / gre_mobile_input missing m_pullup

**Severity:** Medium (CVSS 3.1: `AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H`)
**Class:** CWE-125 OOB read + CWE-787 (downstream write from stale data)
**Location:** `sys/netinet/ip_gre.c:133-164` (`gre_input2`), `sys/netinet/ip_gre.c:210-232` (`gre_mobile_input`)

## Bug

Both GRE input handlers overlay a struct starting at `mtod(m)` and
dereference fields at byte offset >= 20 (right after the 20-byte outer
IP header) **without first calling `m_pullup()`**:

```c
/* gre_input2 — ip_gre.c:133,151,164 */
struct greip *gip = mtod(m, struct greip *);     /* overlay 0..23 */
flags = ntohs(gip->gi_flags);                    /* offset 20-21 */
switch (ntohs(gip->gi_ptype)) {                  /* offset 22-23 */

/* gre_mobile_input — ip_gre.c:210,223,225,229,232 */
struct mobip_h *mip = mtod(m, struct mobip_h *); /* overlay 0..31 */
ntohs(mip->mh.proto);                            /* offset 20-21 */
mip->mh.osrc;                                    /* offset 28-31 */
mip->mh.odst;                                    /* offset 24-27 */
gre_in_cksum((u_short*)&mip->mh, msiz);          /* offset 20..31  */
```

`ip_input` only guarantees `m->m_len >= ip_hl*4`
(`KASSERT` at `ip_input.c:539`). For any packet whose head mbuf carries
**only** the IP header — a short single-mbuf datagram, or a chained
mbuf after `ip_reass` `m_cat` — the bytes at offset >= 20 are NOT in the
head mbuf. The overlay derefs then read **stale / recycled mbuf-cluster
residue** instead of the actual GRE/mobile header fields.

## Reachability

Sibling to DF-0740 / DF-0741 / DF-0743 (same `if_gre` module). A `gre`
interface configured by an admin (`ifconfig greN tunnel ... [up]`) causes
`encap_attach()` to register `in_gre_protosw` (`gre_input` →
`gre_input2`, IPPROTO_GRE=47) and/or `in_mobile_protosw`
(`gre_mobile_input`, IPPROTO_MOBILE=55). Any packet routed to the host
with the matching outer src/dst and protocol then takes the path:

```
ip_input → ip_protox[47|55] → encap4_input → mask_match
  → gre_input2 / gre_mobile_input   (NO m_pullup before offset-20+ derefs)
```

The reachable attacker is **remote**: anyone who can deliver a packet
with the matching outer IP src/dst. Loopback raw-socket send here is
just the test harness.

## PoC

`trigger.c` opens `socket(AF_INET, SOCK_RAW, IPPROTO_RAW)` with
`IP_HDRINCL` and sends two short packets to 127.0.0.1 after root sets
up gre0 (mobile mode) and gre1 (GRE mode):

- **Variant M** (24-byte IPPROTO_MOBILE, S-bit set): `m_len=24`. The
  `odst@24-27`, `osrc@28-31`, and the 12-byte `gre_in_cksum` range all
  extend past `m_len=24` → OOB read (DF-0742). The subsequent `bcopy`
  size `24-12-20=-8` underflows → `memmove` page-fault → panic
  (DF-0741 sibling). **Reliably panics the unpatched kernel.**

- **Variant G** (20-byte IPPROTO_GRE, IP header only): `m_len=20`.
  `gi_flags@20-21` and `gi_ptype@22-23` are entirely past `m_len=20`
  → OOB read of stale cluster data (DF-0742). Outcome is
  residue-dependent (may silently mis-parse or trigger the DF-0740
  hlen underflow).

## Reproduction

**Baseline** (`6.5-DEVELOPMENT #0`, shipped `if_gre.ko`):
- Variant M → `panic: Fatal trap 12: page fault while in kernel mode`
- `memmove+0x24f` → `Stopped at memmove+0x24f: repe movsq`
- ssh dies, `vm.sh status` => down

**Patched** (rebuilt `if_gre.ko` with `fix.diff`):
- Both variants sent, trigger exits 0, x3 consecutive runs
- Guest stays up, NO panic in `boot.log`
- `m_pullup(m, sizeof(struct mobip_h))` drops the 24-byte packet
  (too short to hold the 32-byte overlay) before any offset-20+ deref

## Impact

OOB read + downstream panic (DoS). The stale bytes read by the overlay
derefs are mbuf-cluster residue — not directly attacker-controlled, but
they DO flow into protocol decisions (wrong GRE ptype → mis-parse;
wrong mobile checksum → silent drop; wrong osrc/odst → written into the
re-injected packet's ip_src/ip_dst, a potential info leak if the packet
reaches a recipient). No escalation (read-class primitive, no
attacker-controlled write).

## Fix

`fix.diff` adds `m_pullup(m, sizeof(struct greip))` (24 bytes) to
`gre_input2` and `m_pullup(m, sizeof(struct mobip_h))` (32 bytes) to
`gre_mobile_input`, right after the `gre_lookup` check and before the
first offset-20+ deref. On failure (too-short packet) the mbuf is freed
and the function returns early. `m_pullup` also coalesces chained-mbuf
headers into the head mbuf so the overlay derefs are safe.

## Build single-fix module

`gre` is NOT in the static `X86_64_GENERIC` kernel, so the correct
single-fix build is the module (sibling to DF-0741's approach):

```sh
cd /usr/src/sys/net/gre
make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
cp if_gre.ko /boot/kernel/if_gre.ko
sync; reboot
```

## Files

| file              | purpose |
|-------------------|---------|
| `trigger.c`       | userspace raw-socket injector (IPPROTO_MOBILE + IPPROTO_GRE short packets) |
| `build.sh`        | `cc -O2 -Wall -o trigger trigger.c` |
| `run.sh`          | setup gre-mobile + gre tunnels, run trigger |
| `build.log`       | trigger build output |
| `run.log`         | baseline run output + panic signature + analysis |
| `panic.txt`       | baseline panic signature from `boot.log` |
| `fix.diff`        | standalone git-apply-able fix (m_pullup in both handlers) |
| `fix_build.log`   | single-fix module build output |
| `fix_run.log`     | patched-module trigger output (3 runs, all clean) |
| `env.txt`         | guest uname / cc / sysctls / module sha |
| `VERDICT.md`      | full narrative |
| `manifest.json`   | machine-readable catalog |
