# DF-5808 — VERDICT

**Status: reproduced (deterministic unprivileged OOB kernel heap read;
impact = leak).**

## Root cause chain

1. `sys/kern/vfs_syscalls.c:1281-1284` — `MOUNTCTL_MOUNTFLAGS` is the
   ONLY mountctl op exempt from `caps_priv_check_td(SYSCAP_RESTRICTEDROOT)`:
   any local user may call it on any mount-root path.
2. `sys/vfs/hammer/hammer_vnops.c:2562-2582` — MOUNTCTL_MOUNTFLAGS path:
   `vop_stdmountctl(ap)` stores part1 into `*ap->a_res`
   (sys/kern/vfs_default.c:1316); hammer reads `usedbytes = *ap->a_res`,
   appends the hammer-extra flags (`usedbytes += vfs_flagstostr(...)`),
   then writes `*ap->a_res += usedbytes` — **part1 is counted twice**.
   The actual buffer writes are correctly bounded
   (`vfs_flagstostr(..., ap->a_buflen - usedbytes, ...)`); only the
   *returned byte count* is inflated.
3. `sys/kern/vfs_syscalls.c:1331-1332` — `sys_mountctl` trusts the
   VOP's count: `copyout(buf, uap->buf, sysmsg->sysmsg_result)` with no
   clamp against `uap->buflen`; `buf` is `kmalloc(buflen+1, M_TEMP)`.
   `2*part1+part2 > buflen+1` ⇒ out-of-bounds source read of the
   kernel heap, disclosed to the unprivileged caller.

## Reproduction (guest DragonFly 6.5-DEVELOPMENT, stock kernel #0)

- Root mounted a HAMMER(1) fs with flags
  `ro,noexec,nosuid,nodev,nosymfollow,noatime,noclusterr,noclusterw`
  → visible string `read-only, noexec, ..., local` (85 chars).
- uid 1001: `mountctl("/mnt/h", MOUNTCTL_MOUNTFLAGS, -1, NULL, 0, buf, 86, &res)`:
  - wide probe (buflen=1024): `ret=170`, `strlen(out)=85` → ret == 2*strlen.
  - tuned (buflen=86): `ret=170 > 86`: copyout of 170 bytes from the
    87-byte kernel buffer → 84 bytes OOB (run.log, 3/3 identical).
  - tmpfs control (`/tmp`, buflen=86): `ret=5 == strlen=5` — no
    doubling; the defect is hammer-specific (hammer2 has no mountctl
    override — verified by grep).
- The over-read bytes landed in a zeroed heap region on this guest;
  the read extent (`part1-2`) and determinism are attacker-controlled
  via `buflen`; on a busier heap the same primitive samples adjacent
  M_TEMP allocations. No marker-byte placement was achieved with the
  in-syscall `ctllen` groom (different kmalloc buckets) — documented
  honestly; the structural over-read needs no grooming to exist.

## Fix validation

Applied fix.diff (hammer `*ap->a_res = usedbytes` + result clamp in
sys_mountctl), rebuilt/installed kernel, rebooted:

- `/tmp/mctl2 /mnt/h 86 3` → `ret=85 == strlen=85` on all runs;
  wide probe `ret=85` (run_fixed.log).  Over-read gone.

## Notes

- On hosts whose root/daily filesystems are HAMMER(1) (the DragonFly
  default through 5.x), this is an unprivileged, repeatable kernel
  heap disclosure against a default-config mount. On hammer2-root
  hosts (6.x default) a hammer1 mount must exist (admin setup).

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