# DF-3236 VERDICT — devfs vn_lock(NULL) on unknown-ino file handle

## Bottom line

**REPRODUCED (baseline) / FIXED (patched kernel).** A crafted file handle
whose `fid_gen` passes devfs's boottime check but whose `fid_ino` matches no
devfs node drives `devfs_inode_to_vnode()` into `vn_lock(NULL)` →
`lockmgr_exclusive` NULL-deref → fatal trap 12, guest down. One-line guard
fix validated on a rebuilt kernel: same PoC now returns ENOENT, guest stays
up.

## Root cause chain (all line-verified)

1. `sys_fhopen` (kern/vfs_syscalls.c:4814) — root-gated
   (`SYSCAP_RESTRICTEDROOT`, :4832) — copies a 28-byte `fhandle_t` from
   userland and calls `VFS_FHTOVP` (:4856).
2. `devfs_vfs_fhtovp` (sys/vfs/devfs/devfs_vfsops.c:203-221) checks only
   `fid_gen != boottime.tv_sec` (:211-212) — no fid_len / ino validation —
   then calls `devfs_inode_to_vnode(mp, fid_ino)` (:214).
3. `devfs_inode_to_vnode` (sys/vfs/devfs/devfs_core.c:959-976) sends a sync
   `DEVFS_INODE_TO_VNODE` message to the devfs core.
4. Core handler (devfs_core.c:1373-1378): `msg->mdv_ino.vp =
   devfs_iterate_topology(root, cb, &ino)`. On no match the walker returns
   NULL (devfs_core.c:630-649 — `ret = callback(node, arg1)` with the
   inode_to_vnode callback returning NULL for non-matching nodes,
   devfs_core.c:2196-2214).
5. **The bug**: `devfs_inode_to_vnode` executes
   `vn_lock(vp, LK_EXCLUSIVE | LK_RETRY)` on that NULL *before* returning it
   (devfs_core.c:971-972). The callers' NULL checks
   (devfs_vfsops.c:216-217 for fhtovp, :251-252 for vget) are unreachable.
6. `vn_lock(NULL)` → VOP_LOCK → `lockmgr_exclusive+0x5d` reads
   `NULL->lk_flags`: `Fatal trap 12`, fault VA 0x0.

`devfs_vfs_vget` (devfs_vfsops.c:244-256) feeds the same broken helper with
`ino` from VFS_VGET; VFS_VGET is only reached from the kernel NFS server's
wire-driven directory walk (nfs_serv.c:3417, 3454) — i.e. an exported devfs
would expose this to unauthenticated remote clients (admin action required
to export, so not counted as a default remote exposure).

## Privilege boundary — honestly scoped

- Unprivileged local users CANNOT reach this: every local fh* syscall
  (`getfh`, `fhopen`, `fhstat`, `fhstatfs`, `fhstatvfs`) is gated by
  `SYSCAP_RESTRICTEDROOT`. Verified live: unprivileged run returns EPERM
  before touching devfs. Root trigger → Low severity (robustness bug /
  privileged local DoS, plus latent remote exposure only for an
  NFS-exported devfs).
- No escalation path: the fault is a read of `NULL+const` in lockmgr —
  deterministic panic, nothing controlled.

## Verification log

### Baseline (stock INVARIANTS kernel #0, Jul 2 2026)

- Unprivileged control: `EPERM` (gate works) — never reaches devfs.
- Root run: guest died mid-command (ssh hung);
  serial console (`panic.txt`):
  `Fatal user address access from kernel mode from fhdevfs`,
  `Fatal trap 12: page fault while in kernel mode`,
  `fault virtual address = 0x0`,
  `Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi`,
  `current process = 922` (fhdevfs), `db>` prompt. `vm.sh status` ⇒ down.
- PoC detail: takes a *real* devfs fh via `getfh("/dev/null")` (byte-exact
  fsid+gen), swaps only `fid_ino` to 0x41414141. First attempt using
  sysctl-derived `kern.boottime` failed the gen check (EINVAL) — the
  sysctl's tv_sec does not byte-match the kernel `boottime` global; using
  the real fh fixed it.

### Patched kernel (fix.diff: NULL guard before vn_lock, devfs_core.c:972)

- `make nativekernel` + `make installkernel` in guest (fix_build.log, RC=0).
- New kernel `#1: Wed Sep 9 03:33:19 UTC 2026`.
- Same PoC, root: `survived: fd=-1 errno=2 (No such file or directory)` —
  `devfs_vfs_fhtovp`'s existing `vp == NULL → ENOENT` branch
  (devfs_vfsops.c:216-217) is now reachable. Guest stayed up
  (fix_run.log, uptime confirmations).

## Impact classification

`panic` (kernel NULL-deref DoS), `reproduced=1`, root-gated locally →
severity Low. No memory-corruption primitive (read at fixed NULL offset,
fully deterministic).

## 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.
