# DF-0907 — smbfs_mount signed-underflow heap overflow

## The bug

`sys/vfs/smbfs/smbfs_vfsops.c:165-178` builds `mp->mnt_stat.f_mntfromname`
(a `char[MNAMELEN]`, `MNAMELEN=80` per `sys/sys/mount.h:93`) from
`vcp->vc_username` + `'@'` + `vcp->vc_srvname` using hand-rolled `strncpy`
arithmetic with **no bounds discipline**:

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

If `strlen(vc_username) >= 76`, the line-170 `strncpy` writes **76 bytes
without a NUL terminator**. `index(pc, 0)` then scans past those 76 bytes and
finds the still-zero byte at `buf[78]` (left by the line-167 `bzero`), so
`pc = buf+78`. The `if (pc < pe-1)` guard passes (`buf+78 < buf+79`). The
line-172 `*(pc++)='@'` clobbers that zero and advances `pc` to `buf+79`. The
line-173 `strncpy` then evaluates `pe-pc-2 = (buf+80)-(buf+79)-2 = -1`, which
as a `ptrdiff_t` coerced to `size_t` (the type of `strncpy`'s third argument)
is **`SIZE_MAX`**. `strncpy` dutifully writes `vc_srvname` followed by
~`SIZE_MAX` zero-padding bytes, blowing past `f_mntfromname[80]` into the
adjacent `struct statfs`/`struct mount` fields.

`vc_username` is `smb_strdup(vcspec->username)` (`smb_conn.c:482`), and
`vcspec->username` is `ioc_user` from `struct smbioc_ossn`, which is a fixed
`char[SMB_MAXUSERNAMELEN+1] = char[129]` (`smb_dev.h:76`,
`SMB_MAXUSERNAMELEN=128`, `smb.h:289`). Userspace `mount_smbfs` caps username
length at `SMB_MAXUSERNAMELEN-1 = 127` (`contrib/smbfs/lib/smb/ctx.c:288`),
so any username of **76–127 chars** reaches the underflow.

## Threat model

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. The overflow fires
during `smbfs_mount`'s option parsing, **before any SMB server connection is
attempted** — so no SMB server is needed. On the default GENERIC kernel
(INVARIANTS ON), the `SIZE_MAX` zero-padding write immediately page-faults
into unmapped memory → kernel panic.

## Files

| file          | purpose                                                        |
|---------------|----------------------------------------------------------------|
| `trigger.c`   | kernel trigger: open `/dev/nsmb0` + `SMBIOC_LOOKUP` (long user) + `mount(2)` |
| `harness.c`   | userspace-only deterministic proof that `pe-pc-2 == -1 → SIZE_MAX` |
| `build.sh`    | `cc -o trigger trigger.c && cc -o harness harness.c`           |
| `run.sh`      | run `harness` then `trigger` (as root, after `kldload smbfs`)  |

## Reproduce

```
./build.sh
./run.sh
```

Expected on the vulnerable kernel:
1. `harness` prints `*** CONFIRMED: bound is (size_t)-1 == SIZE_MAX ***`.
2. `trigger` enters `mount(2)`; the kernel panics with a page fault inside
   `strncpy`'s zero-padding loop. The guest goes down and the panic signature
   is captured in `dfbsd-qemu/boot.log`.

Expected on the fixed kernel:
- `harness` still prints the arithmetic confirmation (the model is unchanged).
- `trigger`'s `mount(2)` returns cleanly with a normal errno (the VC was never
  actually connected, so the mount fails — but no panic, no overflow).
