# DF-5710 VERDICT — reproduced (guard-page harness; caller-side unbounded copy)

## What was audited and what was found where

Pass-2 audit of `sys/libkern/strncpy.c` (59 LOC). The implementation itself is
**provably correct** — this is the classic Torek code and the rig proves it:

* **(a) semantics differential** — byte-exact image + return value vs libc
  `strncpy` over the full `(n, srclen) ∈ [0,64]²` matrix plus 100,000 random
  cases with embedded NULs: identical (`run.log`, part A).
* **(d) pad-count math** — for every `(n, srclen)`, exactly `n` bytes are
  written (`[0,k)` = src, `[k,n)` = NUL-pad), canary beyond `n` untouched
  (part B). Hand-trace of `strncpy.c:49-56`: NUL at index k leaves `n` =
  N−k at the pad loop, which writes N−k−1 zeros → exactly N total.
* **(c) guard-page differential** — src guard: NUL inside window + n≫window
  → no fault (pad loop never re-reads src, `strncpy.c:52-53`); no NUL and
  n == window → no fault (reads exactly n). dst guard: n == space → no fault
  (writes exactly n); n == space+1 → SIGSEGV (bound is exactly n; callers
  must size for n). All as specified (parts C0/C1/D0/D1/D2).

So the audited file is clean; per the pass-2 checklist the hunt moved to the
**consumer census** (≈150 in-tree call sites). One caller is genuinely broken:

## The bug (caller): `sys/vfs/smbfs/smbfs_vfsops.c:170-176`

```c
pc = mp->mnt_stat.f_mntfromname;              /* MNAMELEN == 80 */
pe = pc + sizeof(mp->mnt_stat.f_mntfromname);
bzero(pc, MNAMELEN);
*pc++ = '/'; *pc++ = '/';
pc = index(strncpy(pc, vcp->vc_username, pe - pc - 2), 0);   /* :170 */
if (pc < pe-1) {
        *(pc++) = '@';
        pc = index(strncpy(pc, vcp->vc_srvname, pe - pc - 2), 0); /* :173 */
        if (pc < pe - 1) {
                *(pc++) = '/';
                strncpy(pc, ssp->ss_name, pe - pc - 2);      /* :176 */
        }
}
```

The guards `pc < pe-1` are evaluated **before** the separator write and the
`pc++` that follows; the budget `pe - pc - 2` is computed **at the call**,
after the increment. When the cursor reaches `pe-1` (offset 79 of 80), the
budget is `80 - 79 - 2 = -1`, a `ptrdiff_t` passed to `strncpy`'s `size_t`
parameter → `(size_t)-1` → unbounded copy + zero-fill from
`f_mntfromname[79]` through the rest of `struct mount` and adjacent kernel
heap. Two concrete routes (username allowed up to `SMB_MAXUSERNAMELEN=128`,
`smb_dev.h:76` `ioc_user[SMB_MAXUSERNAMELEN+1]`, i.e. 76+ chars is legal input):

* **user route:** `strlen(vc_username) ≥ 76` fills budget1 (`80-2-2`);
  `index` lands at offset 78; `'@'` written at 78; cursor = 79 → budget2 = −1
  → `strncpy(f+79, vc_srvname, SIZE_MAX)` (`smbfs_vfsops.c:173`).
* **share route:** `strlen(vc_username) == 75` → budget2 == 0 (no-op),
  `index` lands at 78, `'/'` at 78, cursor = 79 → budget3 = −1 →
  `strncpy(f+79, ss_name, SIZE_MAX)` (`smbfs_vfsops.c:176`); share names up
  to `SMB_MAXSHARENAMELEN=128`.

In-kernel effect: `struct mount` (`kmalloc`'d, M_MOUNT) is overrun from
`mnt_stat.f_mntfromname` onward — the copy first writes the source string
(≤15/128 attacker-chosen bytes for srvname/share route), then zero-pads
without bound: guaranteed corruption of `struct mount` fields and adjacent
slab objects → panic at best, deliberate heap zeroing at worst. Not a
controlled-write primitive, but a reliable, attacker-shaped DoS/corruption.

## Reproduction (guest, DF 6.5-DEVELOPMENT, cc 8.3)

`harness_smbfs.c` replicates `smbfs_vfsops.c:165-178` verbatim (index→strchr),
with the guest kernel's own `strncpy.c` compiled unmodified, a
`mount`-analogue struct placed against a guard page:

* case0 benign (`joe@fs01/pub`): budgets 76/72/67, clean, `'//joe@fs01/pub'`.
* case1 `76*'u'`: **budget2 = −1 printed at the call point** → SIGSEGV
  crossing the guard page.
* case2 `75*'u'`: **budget3 = −1** on the share strncpy → SIGSEGV.
* case3 (1 MB runway): the pad loop zeroes the whole runway and still dies at
  the far guard — visualization of the kernel-heap zero-fill.

An in-guest kernel trigger (real `mount_smbfs`) was not attempted: reaching
`smbfs_mount` requires an established netsmb VC+SS (live SMB session) —
root-only setup, and the outcome (unbounded kernel copy) is exactly what the
harness demonstrates with the same arithmetic and the same `strncpy`.

## Fix validation

1. `fix.diff` (`pe - pc > 2` guards evaluated at the right points) applies
   clean to guest `/usr/src` (`patch -p1 --dry-run`: Hunk #1 succeeded at 167).
2. Guest builds `sys/vfs/smbfs` → `smbfs.ko` with the fix under `-Werror`.
3. `harness_smbfs_fixed.c` (same rig, guarded caller): every case — including
   both −1 routes — clean exit with correctly truncated, NUL-terminated
   `f_mntfromname`. Baseline behavior for benign inputs unchanged
   (`'//joe@fs01/pub'`).
4. Guest source reverted after validation; guest left clean (`vm.sh status` up).

## Status classification

`reproduced` / impact `dos`-to-`corruption` (kernel heap overrun; not shaped
into a uid0 chain because the trigger is mount-privileged — Dillon legacy
bucket `sys/vfs/smbfs/`), confidence certain. `guest_dirty: 0`.

## kernel_refs

* sys/vfs/smbfs/smbfs_vfsops.c:165-178 (defect)
* sys/netproto/smb/smb_dev.h:76,288-291 (ioc_user 129 bytes; SMB_MAXUSERNAMELEN 128)
* sys/netproto/smb/smb_usr.c:87 (spec->username = dp->ioc_user)
* sys/netproto/smb/smb_conn.c:479-483 (smb_strdup into vcp->vc_username)
* sys/sys/mount.h:93,115 (MNAMELEN 80; f_mntfromname[MNAMELEN])
* sys/libkern/strncpy.c:43-58 (the primitive — correct per rig)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
