# DF-2862 VERDICT

**Status: reproduced (KLD replica with the kernel's own ksscanf/kmalloc
on the live stock kernel #0, plus the real boot path via loader.conf;
fix validated on a rebuilt kernel #1).**
**Impact: leak (kernel heap bytes adjacent to an M_MOUNT 96-byte
allocation read out-of-bounds and copied into `devname`; at boot those
bytes are printed to the console/serial log). Severity Low — the input
channel is the loader environment or the boot console, so this is
host-config / physical-console gated, not remotely or runtime reachable.**

## Root cause (path:line)

1. `sys/kern/vfs_conf.c:421` — `mf = kmalloc(MFSNAMELEN+MNAMELEN, ...)`
   = 96 bytes (`MFSNAMELEN` 16, `MNAMELEN` 80, sys/sys/mount.h:92-93).
2. `sys/kern/vfs_conf.c:427` — `strncpy(mf, cp, MFSNAMELEN+MNAMELEN)`
   with `n == sizeof(mf)`: a candidate remainder >= 96 bytes fills the
   buffer completely and leaves it **without a NUL terminator**
   (the preceding `bzero` at :426 is fully overwritten).
3. `sys/kern/vfs_conf.c:431` — `ksscanf(mf, patt, ...)` →
   `sys/kern/subr_scanf.c:123` `inr = strlen(inp)` — `strlen` runs past
   the end of the 96-byte heap allocation.
4. `sys/kern/subr_scanf.c:265-272` (leading-whitespace skip for `%80s`)
   and `:373-383` (copy loop) continue consulting/copying bytes past
   the allocation; copied bytes land in `devname[80]`.
5. Disclosure sink: `sys/kern/vfs_conf.c:645`
   `kprintf("no disk named '%s'\n", name)` via `setrootbyname`
   (vfs_conf.c:447 → 658-672 → 630-651).

DF-0099 (known) covers the *write* off-by-one of `%16[...]`/`%80s`
against `vfsname[16]`/`devname[80]`; this finding is the *read* overrun
of `mf` — different line, different primitive.

## How it was proven

- **Replica (run.log, run.2.log):** KLD embedding a verbatim copy of
  vfs_conf.c:419-431, run at runtime against the kernel's own
  `ksscanf`/`kmalloc`/`M_MOUNT`. Seeding the class-96 M_MOUNT chunk pool
  with 'Q' (chunks of exactly 96 bytes, packed back-to-back —
  kern_slaballoc.c `zoneindex()` rounds <128 to 8-byte granularity) makes
  the byte at `mf[96]` deterministic. Result: `first96_nonzero=1
  devname[0]=51 devname='QQQQ…'` — bytes from beyond the 96-byte
  allocation copied into `devname`. Reproduced on 2/2 module loads
  (after the correct chunk class was seeded; the first groom attempt
  seeded class-128 and produced the silent variant, which is itself the
  same OOB read terminating on an adjacent NUL).
- **Real boot path (bootpath-baseline.log):** loader.conf
  `vfs.root.mountfrom="ufs:da0s1a;ufs:<92 spaces>E;hammer2:vbd0s1d"`,
  stock kernel #0. Candidate 2's parse consumed all 96 non-NUL in-bounds
  bytes and stopped exactly at `mf[96]` (read out of bounds; silent
  because the adjacent byte happened to be NUL — consistent with the
  replica's unseeded variant). The boot log proves the kenv string of
  >= 96 bytes reaches the vulnerable copy at boot.
- **Console disclosure sink:** exercised at boot by candidate 1
  (`no disk named 'da0s1a;ufs:'` printed) — same `kprintf` that would
  print OOB-sourced `devname` bytes whenever the adjacent heap byte is
  non-NUL/non-space.

## Exploit chain

None (by design): boot-time parse, loader/console-gated. Ceiling is
disclosure of adjacent M_MOUNT-zone heap bytes to the boot console /
serial log, and parse misbehaviour (device lookups on garbage). No
runtime unprivileged reachability: `vfs_mountroot_try` is static, called
only from `vfs_mountroot` (SYSINIT) and `vfs_mountroot_ask`; kenv is
read-only at runtime (kern_environment.c:503, `kern.environment`
CTLFLAG_RD).

## Fix validation

`fix.diff`: bound the copy at the `;`-segment (`ep`) and force
termination — `seglen = min(ep - cp, MFSNAMELEN+MNAMELEN - 1)`;
`bcopy(cp, mf, seglen)` into the pre-bzeroed buffer.

- baseline (kernel #0, stock): contamination + unterminated 96-byte copy
  (bootpath-baseline.log).
- patched (kernel #1 `Wed Sep 2 13:46:57 UTC 2026`, same loader.conf):
  `no disk named 'da0s1a'` — clean segment; candidate 2 parse bounded
  at 95 with guaranteed NUL; no out-of-bounds consult; machine boots
  normally via candidate 3 (bootpath-patched.log).
- Replica A/B (BUGGY vs FIXED parse in the same module) shows devname
  sourced from OOB bytes vs '' cleanly (run.log B1 vs B2).

The fix also fixes DF-2863 (same hunk). Guest restored with
`vm.sh reset with-src` after validation.
