# DF-3286 — mesh_decap stack OOB write: reserved AE=11 mesh control grows header copy to 56 bytes into 50-byte stack buffer `b[]`

File: `sys/netproto/802_11/wlan/ieee80211_mesh.c` (pass 2 audit)

## What

`mesh_input` (:1738-1741) computes the mesh-control length from the
attacker-supplied Address Extension bits **without rejecting the reserved
value AE=11 (3)**:

```c
ae = mc->mc_flags & IEEE80211_MESH_AE_MASK;          /* 0..3, 3 = RESERVED */
meshdrlen = sizeof(struct ieee80211_meshcntl)         /* 6  */
    + ae * IEEE80211_ADDR_LEN;                        /* 3*6 = 18 -> 24 */
hdrspace += meshdrlen;                                /* 32 + 24 = 56 */
```

`mesh_decap` (:1281-1298) then copies the full header into a stack buffer
sized for the **legal** maximum only:

```c
uint8_t b[sizeof(struct ieee80211_qosframe_addr4)    /* 32 */
          + sizeof(struct ieee80211_meshcntl_ae10)]; /* 18 -> 50 */
...
memcpy(b, mtod(m, caddr_t), hdrlen);                 /* hdrlen = 56 */
```

A 4-address (DSTODS) QoS mesh **multicast** data frame with
`mc_flags & 3 == 3` from an established mesh peer writes **6
attacker-controlled bytes past `b[]`** (the last 6 bytes of the 24-byte
mesh control, i.e. the would-be `mc_addr6`).  The multicast path has no
AE validation anywhere (`to_me`/`to_fwrd` are unicast-only), so this
fires on INVARIANTS *and* production kernels.

## Runtime proof on the stock kernel (X86_64_GENERIC #0, INVARIANTS)

1. **Disassembly of the shipped kernel** (mesh_decap inlined into
   mesh_input):
   ```
   ffffffff80779e62: lea    -0x62(%rbp),%r14        # b = rbp-0x62 (50 bytes: .. rbp-0x31)
   ffffffff80779e73: mov    -0x98(%rbp),%rdx
   ffffffff80779e7a: rep movsb %ds:(%rsi),%es:(%rdi) # count = -0x90(%rbp) = hdrlen = 56
   ```
   The copy provably runs 56 bytes into the 50-byte object; the 6 extra
   bytes land at `rbp-0x30..rbp-0x2b`, which in *this* build is stack
   padding (saved registers start at rbp-0x28), so the guest survives —
   a silent, layout-latent kernel stack overwrite.

2. **Reachability**: injecting the frame via the harness
   (`sysctl kern.fwm_step=10`) on the stock guest leaves the guest up and
   increments `is_mesh_badae` — the frame provably reaches mesh_decap's
   post-memcpy AE dispatch (ieee80211_mesh.c:1339), i.e. the memcpy has
   already executed.  ×21 frames, still up (padding absorbs it here).

## Build / run (repo root)

```
sh findings/poc/DF-3286/build.sh    # builds /root/fwm/fwm.ko in the guest
sh findings/poc/DF-3286/run.sh      # brings mesh vap up, peers, fires step 10
```

Harness: `fwm.c` — software net80211 radio + RX frame injector + TX
capture (see README in this dir). The peering dance (beacon → OPEN →
CONFIRM, all synchronous) puts the victim in
`IEEE80211_NODE_MESH_ESTABLISHED`, after which a single 68-byte crafted
multicast data frame triggers the overflow.

## Expected output

```
fwm: wlan0 RUN, beacon allocated
fwm: inject BEACON ... / inject PEER OPEN / inject PEER CONFIRM
fwm: inject MCAST AE=3 data (68 bytes; decap hdr=56 > b[50])
kern.fwm_stats: is_mesh_badae=1 ...      # memcpy executed, frame then discarded
GUEST_STILL_UP                             # 6 bytes hit padding on this build
```

Patched (fix.diff kernel): the AE=11 frame is discarded at mesh_input
before any decap math; same run shows no decap-path activity for the
frame and normal delivery for legal AE values.

## Threat

Remote, unauthenticated-after-peering (victim auto-peers with any mesh
STA by default, `ms_flags = AP|FWD` at :683).  6 controlled bytes of
kernel-stack overwrite per frame; on this exact gcc-8.3 INVARIANTS build
they land in padding, on any rebuild/layout where `b[]` is adjacent to
live locals or spill slots this becomes stack corruption
(potential control-flow influence). CWE-787/CWE-121.
