# DF-5010 VERDICT — vdisk missing bounds check: **shielded by dscheck(); not exploitable today; hardening fix validated**

## Reproduced?

**The OOB itself: NO — and that is the verified security conclusion.**
The driver-level missing check is real, but every path a guest can drive into
`vkdstrategy()` is bounded by the generic slice layer before `vkd_doio()` sees
the bio. Verified three ways:

1. **Code trace (the shield):**
   * Every devfs-visible vkd node is a *disk-core covering device*
     (`disk1_ops`/`disk2_ops`, `sys/kern/subr_disk.c:716-745`), whose
     `d_strategy` is `diskstrategy` → `dscheck()`
     (`sys/kern/subr_disk.c:1240-1246`, `sys/kern/subr_diskslice.c:91`).
   * `dscheck()` rejects negative offsets (subr_diskslice.c:110), enforces
     sector alignment (:131-143), and bounds `secno + nsec` against the
     slice/disk end with truncate/EOF/EINVAL semantics (:253-277).
   * The only dscheck *bypass* in the system — whole-disk raw-access
     partitions ≥ 128, "fed directly through to the device with no further
     interpretation" (subr_diskslice.c:171-175) — requires dev_t minors that
     vdisk never creates (only `fd.c:1036-1054` creates such nodes).
   * The dump path is separately bounded (`diskdump()` → `disk_dumpcheck()` +
     explicit range check, sys/kern/subr_disk.c).
2. **Empirical, run 1 (`run.log`, unaligned 4-byte I/O):** kernel printed
   `dscheck(vkd1): b_bcount 4 is not on a sector boundary` and
   `dscheck(vkd2): …` for every attempt — proving dscheck is interposed on
   the raw whole-disk nodes the PoC opened.
3. **Empirical, runs 2-3 (`run.2.log`, `run.3.log`, 512-aligned):** all six
   OOB attempts (write @1 TB, write @size+512, read @1 TB, on both plain and
   COW disks) completed **without any kernel-mode fault** — a wild COW `bcopy`
   would have killed the vkernel host process before "init died" appeared —
   the PoC ran to completion (init exit 0), and `plain.img` never grew
   (16777216 bytes before and after; md5 unchanged): the 1 TB `pwrite` never
   reached the backing file. The aligned bounds-rejection path in dscheck
   (:259 → :294) is silent, which is why no diagnostic appears.

## The latent hazard (why this is still filed)

* COW mode (`-R`, flags & 1): `vkd_doio()` does
  `bcopy(sc->map_buf + bio->bio_offset, bp->b_data, bp->b_bcount)`
  (vdisk.c:310-314, :322-326) — `sc->map_buf` is a host-process `mmap` of
  length `sc->size` (vdisk.c:133-141). An unbounded bio is an
  **arbitrary-offset OOB read/write inside the vkernel host process**
  (guest-root → host-process memory corruption; VM-sandbox break).
* Regular mode: `pwrite()` at an unbounded offset silently *extends* the host
  backing file (sparse growth to attacker-chosen size — host disk-fill).
* DragonFly's own `fd.c` demonstrates that dscheck-bypassing raw partitions
  are a supported driver feature; the day vkd grows them, the missing check
  becomes exploitable. FreeBSD's md(4) checks bounds in-driver for exactly
  this reason.

## Secondary finding (killed): `b_resid` underflow

`bp->b_resid = bp->b_bcount - n` (vdisk.c:346) with `n = -1` (pread/pwrite
error, e.g. NFS-backed disk going down, ENOSPC) computes `resid = bcount + 1`.
`physio()` then computes `iolen = b_bcount - b_resid = (size_t)-1` and calls
`copyout(bp->b_data, ubase, iolen)` (sys/kern/kern_physio.c:104). On x86-64
`std_copyout` pre-checks address wrap (`udaddr + len` carry → immediate
EFAULT, `sys/platform/pc64/x86_64/support.s:247-249`), so **no bytes are
copied** — the disclosure is dead; only uio accounting wraps (loop exits with
the I/O error). Clamped anyway in `fix.diff` for correctness.

## Fix validation

* `fix.diff` applied to the guest's `/usr/src` (checksum-identical to the
  audit tree), vkernel rebuilt (`build-fix.log`, 21:35 kernel.stripped),
  rerun (`run.4-fixed.log`): identical clean behaviour — COW disk armed,
  PoC completed (init exit 0), vkernel survived, `plain.img` md5 unchanged.
  No regression; the driver-level rejection is now armed as defense-in-depth.
* Baseline vs patched: baseline (stock) did not reproduce the OOB (shielded);
  patched also does not reproduce; in-bounds I/O unaffected.

## Environment / build notes

* Guest: DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC (see `env.txt`).
* vkernel64 is currently **un-buildable in HEAD**: `conf/Makefile`'s
  `SYSTEM_LD` override drops the `-Wl,-T,ldscript.x86_64` that defines
  `__build_id_start/__build_id_end`, which `kern/kern_mib.c:102-103` now
  references → link failure (see `build.log`). Worked around guest-side with
  `-Wl,--defsym` (not part of the fix — upstream should either re-add the
  ldscript to SYSTEM_LD or guard kern_mib.c).
* vkernel assets: `sysctl vm.vkernel_enable=1`, `/var/vkernel` must exist.

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