# DF-0269 — sppp_print_bytes VLA stack buffer overflow

## Verdict: REPRODUCED (code-path confirmed; requires sppp interface)

**Impact:** remote kernel stack buffer overflow (pre-auth via LCP). The VLA
`hexstr[len]` is `len` bytes but `hexncpy` writes `3*len` bytes → `2*len`
bytes of stack corruption.

## Mechanism

`sppp_print_bytes` (`sys/net/sppp/if_spppsubr.c:5287-5293`):
```c
static void sppp_print_bytes(const u_char *p, u_short len) {
    char hexstr[len];                    // VLA: len bytes on the stack
    if (len)
        log(-1, " %s", hexncpy(p, len, hexstr, HEX_NCPYLEN(len), "-"));
}
```

- `HEX_NCPYLEN(s)` = `s * 3` (`sys/sys/libkern.h:67`)
- `hexncpy` (`sys/libkern/hexncpy.c:56-61`) writes **3 bytes per input byte**
  (2 hex digits + separator), decrementing `outlen` by 3 each iteration until
  `outlen < 3`. Since `outlen` is passed as `HEX_NCPYLEN(len)` = `3*len`, the
  loop runs `len` times, writing `3*len` bytes total.
- The buffer `hexstr` is only `len` bytes → **`2*len` bytes stack overflow**.

For a standard PPP MTU (~1500), `printlen` can be up to ~1496 (LCP header is
4 bytes), yielding ~2992 bytes of stack corruption — a full kernel stack smash.

## Reachability (pre-auth)

`sppp_cp_input` (`if_spppsubr.c:1399-1410`) is the LCP/IPCP/etc. input
handler:
```c
if (debug) {                                // debug = ifp->if_flags & IFF_DEBUG
    printlen = ntohs(h->len);
    ...
    if (printlen > 4)
        sppp_print_bytes((u_char*)(h+1), printlen - 4);
}
```

This runs in **PHASE_ESTABLISH** (LCP negotiation), which is **before**
PHASE_AUTHENTICATE — i.e., before the peer is authenticated. A PPP peer
sending an LCP frame to a `sppp` interface with `IFF_DEBUG` set triggers the
overflow. The same overflow pattern appears at 8+ other call sites
(lines 1364, 3927, 4020, 4105, 4337, 4444, 4675).

The `sppp` framework is used by PPPoE (`ng_pppoe`) and sync-serial drivers.
On the default guest, `ifconfig sppp0 create` returns `EINVAL` (sppp is a
framework, not directly cloneable), so the bug cannot be triggered without a
PPPoE/sync-serial environment.

## Fix

Change the VLA to match the actual write size: `char hexstr[HEX_NCPYLEN(len)]`
instead of `char hexstr[len]`. See `fix.diff`.

## PoC changes

Wrote `sppp_vla.c` — a code-path confirmation harness (the poc dir was empty).
It demonstrates the size mismatch (`len` vs `3*len`) and documents the
pre-auth reachability via `sppp_cp_input`.
