# DF-0671 — NetBIOS retarget unbounded recursion (smbfs/netsmb)

## Verdict: REPRODUCED (recursion stack-overflow primitive proven; fix validated)

`nbssn_rq_request()` in `sys/netproto/smb/smb_trantcp.c` handles the NetBIOS session
setup. On receiving `NB_SSN_RTGRESP` (0x84) it disconnects, reconnects to the retarget
address, and **recurses into itself with no depth counter**:

```c
/* smb_trantcp.c:261-265 */
nbp->nbp_state = NBST_RETARGET;
smb_nbst_disconnect(nbp->nbp_vc, td);
error = nb_connect_in(nbp, &sin, td);
if (!error)
    error = nbssn_rq_request(nbp, td);   /* <-- unbounded self-recursion */
```

`NBNS_MAXREDIRECTS` is `#define`d to 3 in `netbios.h:110` but **referenced nowhere** in
the kernel — the bound was intended but never enforced. A malicious SMB server that
always answers session setup with a retarget drives the client into unbounded recursion;
each level's frame (`sockaddr_in sin` + `mbchain`/`mdchain` + `mbuf*` + scalars + the
`nbssn_recv`/`socket_wait`/`nb_connect_in` frames above it, ~272 bytes) eats the 16 KB
lwkt stack → overflow.

## Primitive proof (decisive)

The harness `nb_recurse.c` replicates `nbssn_rq_request`'s per-level frame and the
unbounded self-call on a 16 KB lwkt stack. Loading it (`bound=0`, baseline):

```
DF0671: NB retarget recursion level 8  (sin @0xfffff80118616f68)
DF0671: NB retarget recursion level 16 (sin @0xfffff801186166e8)   (-0x880 / 8 levels)
DF0671: NB retarget recursion level 24 (sin @0xfffff80118615e68)
DF0671: NB retarget recursion level 32 (sin @0xfffff801186155e8)
panic: double fault
Stopped at      Debugger+0x7c:  movb    $0,0xbdaf09(%rip)
```

The stack-guard page is hit after ~36 levels → double-fault panic. This is the cited
line-265 pattern isolated from a sibling gate (below).

## Reachability & a sibling gate (important nuance)

The realistic trigger is a malicious SMB server; the victim is any local process that
connects through the netsmb transport (`mount_smbfs`, `smbutil`). `nsmb_dev_open` has
**no privilege check** and the clone device is open to whoever devfs permits.

A network PoC (`evil_nbserver.c` + `mount_smbfs`) **does reach the retarget path**: the
evil server receives a genuine `NB_SSN_REQUEST` (type 0x81) and the kernel parses the
`RTGRESP` and attempts the retarget. However, on this guest the recursion **did not
execute via the live path** because of a distinct sibling bug: the retarget `sin`
(`smb_trantcp.c:258-260`) sets only `sin_addr` and `sin_port`, leaving `sin_len` and
`sin_family` as **uninitialized stack garbage**. `in_pcb.c:655` rejects
`sin_family != AF_INET` with `EAFNOSUPPORT`, so `nb_connect_in()` fails before line 265.
The harness above removes that gate (initializes `sin`) to prove the recursion primitive
in isolation.

So: the unbounded recursion is a **real, confirmed latent bug**; its live trigger is
masked on this kernel by the uninitialized-sockaddr sibling, but it fires deterministically
the moment `sin_family` residue is `AF_INET` (or once that sibling is fixed). The fix
addresses both.

## Escalation ceiling

This is a **stack overflow**, not a heap write — there is no data-only primitive to
groom. On a kernel without stack-cookies/stack-protector for the overflow direction,
unbounded recursion yields a deterministic DoS (panic). Code-exec from a kernel stack
overflow would require a controllable overwrite of a return address with stack residue
shaping, which is not derivable from this primitive on its own; the realistic impact is
**local (or remote-via-evil-SMB-server) kernel panic / DoS**, with RCE only via a
separate return-address-control chain. Driven by an unprivileged user via smbutil/mount,
this is a local DoS; the "unauthenticated remote" framing applies only when the link has
no LCP auth.

## PoC changes

- `evil_nbserver.c` + `run.sh`: an evil NetBIOS server that always retargets to itself,
  driven by `mount_smbfs`. Proves the kernel reaches the retarget path (real
  `NB_SSN_REQUEST` observed, `RTGRESP` parsed).
- `nb_recurse.c` + `Makefile.recurse`: kernel harness replicating `nbssn_rq_request`'s
  frame + the unbounded self-call, with a `bound` tunable. `bound=0` → double-fault
  panic (the primitive); `bound=1` → clean return after 3 levels (the fix).

## Fix (fix.diff)

1. Add `int depth` to `nbssn_rq_request()`, enforced against `NBNS_MAXREDIRECTS`
   (`if (depth >= NBNS_MAXREDIRECTS) return ECONNREFUSED;`); recursive call passes
   `depth + 1`, the entry call in `smb_nbst_connect` passes `0`.
2. Initialize the retarget sockaddr (`bzero(&sin,...); sin.sin_len=...; sin.sin_family=AF_INET;`)
   before `md_get_mem` fills `sin_addr` — closing the sibling uninit that masked the
   recursion on this guest.

Validated: harness `bound=0` panics (double fault) → `bound=1` returns cleanly at
level 3; the real `smbfs.ko` module builds with the patch (build.log).

## Kernel references (verified)
- `sys/netproto/smb/smb_trantcp.c:201` — `nbssn_rq_request` definition (no depth param)
- `sys/netproto/smb/smb_trantcp.c:258-265` — retarget + unbounded self-recursion
- `sys/netproto/smb/smb_trantcp.c:484` — sole external caller `smb_nbst_connect`
- `sys/netproto/smb/netbios.h:110` — `#define NBNS_MAXREDIRECTS 3` (unused)
- `sys/netproto/smb/netbios.h:74` — `#define NB_SSN_RTGRESP 0x84`
- `sys/netinet/in_pcb.c:655,956` — `sin_family != AF_INET` → EAFNOSUPPORT (the sibling gate)
- `sys/netproto/smb/smb_dev.c:99` — `nsmb_dev_open` (no privilege check)
