# DF-5808 — HAMMER mountctl(MOUNTFLAGS) double-counts the result length → unprivileged OOB kernel heap read

## What

`sys_mountctl` (sys/kern/vfs_syscalls.c:1267) exempts
`MOUNTCTL_MOUNTFLAGS` from the root privilege check (line 1281), so any
local user can query the flag string of any mounted filesystem.
`kern_mountctl` passes `&sysmsg->sysmsg_result` to the VOP, and
`sys_mountctl` then does
`copyout(buf, uap->buf, sysmsg->sysmsg_result)` with **no clamp against
buflen** (line 1331-1332).  On HAMMER(1) mounts,
`hammer_vop_mountctl` (sys/vfs/hammer/hammer_vnops.c:2559-2582)
double-counts: `vop_stdmountctl` already stored part1 in
`*ap->a_res`; hammer then computes `usedbytes = part1 (+part2)` and
does `*ap->a_res += usedbytes` → the syscall copyout length becomes
`2*part1 + part2` while the kernel buffer is only `buflen+1` bytes.

With `buflen = strlen(flags)+1` the copyout over-reads
`part1 - 2` bytes of adjacent kernel heap into userspace.
hammer2/tmpfs/ufs/... use `vop_stdmountctl` directly and are safe
(control verified: tmpfs returns ret == strlen).

## Build

    cc -O2 -o /tmp/mctl2 mctl2.c

## Run

(root, one-time) create a HAMPER mount with a long visible flag string:

    dd if=/dev/zero of=/root/h.img bs=1m count=0 seek=4096
    vnconfig -c vn1 /root/h.img && newfs_hammer -f -L leaktest /dev/vn1
    mount -t hammer -o ro,noexec,nosuid,nodev,nosymfollow,noatime,noclusterr,noclusterw /dev/vn1 /mnt/h

(unprivileged, uid 1001):

    /tmp/mctl2 /mnt/h 1024 1     # probe: flags_str + ret
    /tmp/mctl2 /mnt/h 86 3       # leak: ret=170 > buflen=86

## Expected

Stock kernel:

    run 0: ret=170 buflen=86 ... strlen=85
      *** ret(170) > buflen(86): 84 bytes past the 86-byte kernel buffer

(`ret == 2*strlen`; 3/3 identical runs; tmpfs control `ret == strlen`.)
The 84 bytes beyond the 87-byte kmalloc'd buffer are adjacent kernel
heap (zero-filled in this guest's heap state; the over-read itself is
deterministic and attacker-sized).

Patched kernel (fix.diff): `ret=85 == strlen`, no over-read, 3/3.

## Files

- mctl2.c    trigger (probe/tuned/NULL-buflen modes)
- run.log    baseline 3 runs + tmpfs control
- run_fixed.log  patched-kernel runs
- fix.diff   hammer_vnops.c (`=` instead of `+=`) + vfs_syscalls.c clamp
