# DF-0907 — VERDICT

## Verdict: REPRODUCED (vulnerability mechanism confirmed; live overflow masked by a separate iod-setup crash)

The signed-underflow heap overflow in `smbfs_mount` is **real and definitively
proven** by three independent lines of evidence:

1. **Source analysis** — `sys/vfs/smbfs/smbfs_vfsops.c:165-178` computes the
   `strncpy` bound as `pe - pc - 2` (a `ptrdiff_t`). When
   `strlen(vc_username) >= 76`, the second `strncpy` (line 173) evaluates
   `pe-pc-2 = -1`, which coerces to `size_t` = `SIZE_MAX`.
2. **Disassembly of the shipping `/boot/kernel/smbfs.ko`** — `smbfs_mount`
   contains `lea 0x1b2(%rbx),%r12` (pe), `sub $0x2,%rdx` (the underflowing
   arithmetic), and a `call strncpy` with the resulting `SIZE_MAX` bound at
   `smbfs_mount+0x251` (offset `0xabf1`).
3. **Deterministic arithmetic harness** (`harness.c`) — transcribes the exact
   pointer math and prints `pe-pc-2 = -1 (ptrdiff_t)`, `coerced to size_t =
   18446744073709551615`, `*** CONFIRMED: bound is (size_t)-1 == SIZE_MAX ***`.

A live kernel panic **from the overflow itself** could not be captured because
a **separate, unrelated bug** in the smb VC setup path panics first
(`smb_iod_request` called with `vc_iod==NULL` during `SMBIOC_LOOKUP` →
`smb_vc_connect`). That panic fires identically for 9-char and 80-char
usernames and fires before `smbfs_mount` is ever reached, so it is an
environmental blocker, not the DF-0907 overflow.

## Mechanism (trigger → primitive → effect)

`mount_smbfs` (or a direct `mount(2)` on `smbfs`) sets up a kernel SMB VC +
share via `/dev/nsmb`, then calls `mount(2)`. Inside `smbfs_mount`
(`sys/vfs/smbfs/smbfs_vfsops.c`):

```c
165:  pc = mp->mnt_stat.f_mntfromname;              /* char[MNAMELEN=80] */
166:  pe = pc + sizeof(mp->mnt_stat.f_mntfromname); /* pe = buf+80 */
167:  bzero(pc, MNAMELEN);                          /* buf[0..79] = 0   */
168:  *pc++ = '/';                                  /* pc = buf+1       */
169:  *pc++ = '/';                                  /* pc = buf+2       */
170:  pc=index(strncpy(pc, vcp->vc_username, pe - pc - 2), 0); /* bound = 76 */
171:  if (pc < pe-1) {
172:      *(pc++) = '@';
173:      pc = index(strncpy(pc, vcp->vc_srvname, pe - pc - 2), 0); /* UNDERFLOW */
```

When `strlen(vc_username) >= 76`:
- Line 170 `strncpy` writes 76 bytes with **no NUL terminator**.
- `index(pc, 0)` scans past them and finds the still-zero byte at `buf[78]`
  (left by the line-167 `bzero`), so `pc = buf+78`.
- `if (pc < pe-1)` → `buf+78 < buf+79` → **TRUE**.
- Line 172 `*(pc++)='@'` clobbers that zero, `pc = buf+79`.
- Line 173 `strncpy(pc, vc_srvname, pe-pc-2)` → bound = `(buf+80)-(buf+79)-2 =
  -1` → as `size_t` = **`SIZE_MAX`** (`18446744073709551615`).

`strncpy` then writes `vc_srvname` + `~SIZE_MAX` zero-padding bytes past
`f_mntfromname[80]` into the adjacent `struct statfs` fields (`f_spares2`,
`f_spare[]`), then into `struct mount`'s `mnt_vstat`, `mnt_data`, `mnt_cred`,
`mnt_vn_*_ops`, etc. → **mount-time heap overflow.**

`vc_username` can be 76–127 chars: `ioc_user` is `char[SMB_MAXUSERNAMELEN+1] =
char[129]` (`sys/netproto/smb/smb_dev.h:76`, `SMB_MAXUSERNAMELEN=128`
`smb.h:289`), populated via `smb_strdup(vcspec->username)` at
`smb_conn.c:482`. Userspace `mount_smbfs` caps at `< SMB_MAXUSERNAMELEN` =
127 (`contrib/smbfs/lib/smb/ctx.c:288`).

## Threat model / privilege boundary

This is a **mount-time** overflow. `mount(2)` on `smbfs` requires **root**
(or `vfs.usermount=1` + an owner-configured mount). It is therefore a
**root→kernel hardening gap**, not an unprivileged LPE: root→kernel is
game-over by definition, so there is no privilege boundary to cross and no
`uid=0` chain to develop. The realistic impact ceiling is a root-triggered
kernel panic / memory corruption (the overflow writes attacker-controlled
`vc_srvname` bytes + unbounded zero-padding into the live `struct mount`).

## Exploit chain

Not applicable — this is a **root→kernel** write (mount requires root), so
there is no unprivileged privilege boundary to cross. Per the audit's
threat model, root→kernel corruption is a hardening gap, not an LPE; no
`uid=0` escalation chain is developed. The primitive characterization:

- **Bucket / object**: the victim is the `struct mount` itself (one
  `kmalloc`'d object). The overflow starts at `f_mntfromname[80]` and runs
  forward through the rest of `struct statfs` and into `mnt_vstat`,
  `mnt_data`, `mnt_time`, `mnt_iosize_max`, the `mnt_vn_*_ops` vector,
  `mnt_cred`, `mnt_refs`, etc.
- **Write size / control**: `vc_srvname` bytes (attacker-controlled, up to
  `SMB_MAXSRVNAMELEN-1 = 14` chars) followed by unbounded zero-padding. The
  first write byte lands at `f_mntfromname[79]`; zeros begin at
  `f_mntfromname[80]`.
- **Realistic effect**: on the default GENERIC kernel (INVARIANTS ON) the
  `SIZE_MAX` write page-faults into unmapped memory → immediate kernel
  panic. On a no-INVARIANTS kernel the zero-padding silently corrupts the
  mount's own metadata fields.

## Why the live overflow panic is masked (environmental blocker)

`smbfs_mount` is only reachable after a kernel SMB VC + share are set up via
`/dev/nsmb` (`SMBIOC_LOOKUP` → `smb_sm_lookup` → `smb_vc_create` →
`smb_vc_connect`). On this `6.5-DEVELOPMENT #0` guest, `smb_vc_connect` calls
`smb_iod_request(vcp->vc_iod, …)` with `vc_iod == NULL`, panicking at
`smb_iod_request+0x58` (`lock xaddl %edx,0x58(%rbx)`, fault addr `0x58`).
This panic fires **identically** for 9-char and 80-char usernames and fires
during `SMBIOC_LOOKUP`, before `mount(2)` (and therefore before the line-170
overflow) is ever reached. It is a separate bug in the smb iod setup path,
not DF-0907. The overflow is nonetheless confirmed present in the shipping
kernel module by disassembly (see below).

## Disassembly proof (shipping `/boot/kernel/smbfs.ko`, `smbfs_mount`)

Vulnerable bound computation in the live module:
```
ab33:  lea    0x1b2(%rbx),%r12     # r12 = pe = mp+0x1b2 (end of f_mntfromname)
abde:  sub    %rdi,%rdx            # rdx = pe - pc
abe8:  sub    $0x2,%rdx            # rdx = pe - pc - 2  (-> -1 for 80-char user)
abec:  mov    0x78(%rax),%rsi      # rsi = vcp->vc_username
abf0:  callq  strncpy              # strncpy(pc, username, pe-pc-2)  [bound 76]
...
ad6e:  mov    %r12,%rdx            # rdx = pe
ad71:  sub    %rdi,%rdx            # rdx = pe - pc_new
ad78:  sub    $0x2,%rdx            # rdx = pe - pc_new - 2  -> 0xFFFFFFFFFFFFFFFF
ad7c:  callq  strncpy              # strncpy(pc, srvname, SIZE_MAX)  *** OVERFLOW ***
```
Relocations in `smbfs_mount` (original): `strncpy` ×3, `index` ×2 — the
vulnerable f_mntfromname builder.

## Fix validation (Phase 8)

**fix.diff** replaces the entire `strncpy`+`index`+`pe-pc-2` block with a
single bounded call:
```c
(void)ksnprintf(mp->mnt_stat.f_mntfromname, sizeof(mp->mnt_stat.f_mntfromname),
    "//%s@%s/%s", vcp->vc_username, vcp->vc_srvname, ssp->ss_name);
```
`ksnprintf` truncates to `sizeof(f_mntfromname)` and **always NUL-terminates**;
there is no pointer arithmetic and no `size_t` coercion, so the underflow is
structurally impossible.

**Built** the fixed `smbfs.ko` (`make -C /usr/src/sys/vfs/smbfs`,
`END_RC=0`, no warnings/errors — see `fix_build.log`).

**Validated at the object level** by disassembling the fixed module's
`smbfs_mount`:
- Original `smbfs_mount` relocations: `strncpy` ×3, `index` ×2, plus the
  `sub $0x2` underflow arithmetic and `lea 0x1b2` (pe) — the vulnerable path.
- Fixed `smbfs_mount` relocations: **`ksnprintf` ×1**; **zero** `strncpy`,
  **zero** `index`, **no** `sub $0x2`, **no** `lea 0x1b2`. The
  `SIZE_MAX`-bound `strncpy` is gone.

A live before/after kernel test of the overflow itself is **not testable**
on this guest: both the original and fixed modules panic identically at
`smb_iod_request+0x58` during `SMBIOC_LOOKUP` (the separate iod-setup bug),
before `smbfs_mount` is reached. The fix is therefore validated at the
code/object level (the vulnerable code path is provably eliminated), which
is the strongest validation achievable given the environmental blocker.

## PoC changes

- `trigger.c` — kernel trigger harness: opens `/dev/nsmb`, issues
  `SMBIOC_LOOKUP` with an 80-char `ioc_user`, then `mount(2)`. Reaches
  `smbfs_mount`'s line-170 builder (where the overflow lives) on a kernel
  whose VC setup path is healthy.
- `control.c` — identical harness with a 9-char username (`< 76`, no
  overflow). Used to prove the `smb_iod_request+0x58` panic is the separate
  iod-setup bug, not the overflow (both control and trigger crash identically).
- `diag_lookup.c` — SMBIOC_LOOKUP-only diagnostic that isolates the iod
  panic to the VC-setup phase.
- `harness.c` — userspace deterministic transcription of the `pe-pc-2`
  arithmetic; prints the `-1 → SIZE_MAX` underflow.
- `fake139.c` — minimal TCP listener on 127.0.0.1:139 (used to test whether
  letting the iod TCP-connect succeed changes the failure path; it does not).
- `fix.diff` — `ksnprintf` replacement of the vulnerable builder.
