# DF-0141 — Missing privilege check in `sys_vquotactl`

## Verdict: REPRODUCED + FIX VALIDATED

`sys/kern/vfs_quota.c:sys_vquotactl` (syscall 530) contains **no privilege
check** anywhere in its body. Any unprivileged local user can read every
user's disk usage, set/remove per-uid and per-gid quota limits (including
root's), and set the filesystem-wide `ac_limit` — which, when set below
current usage, denies **all** writes on the entire filesystem for **all**
users (filesystem-wide write DoS). The fix adds
`caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR)`, matching the UFS quota
ioctl model, and was built, booted, and verified.

## Mechanism (trigger → primitive → effect)

1. **Reachability gate.** `sys_vquotactl` (`sys/kern/vfs_quota.c:328`)
   begins with `if (!vfs_quota_enabled) return EOPNOTSUPP;` (line 342).
   `vfs_quota_enabled` is a boot tunable (`vfs.quota_enabled=1` in
   `/boot/loader.conf`), `CTLFLAG_RD` so not settable at runtime. An admin
   who deploys VFS quotas sets this; it is a realistic deployment
   precondition, not part of the exploit chain.

2. **No privilege check.** After the gate, the function does
   `nlookup` on the user-supplied path (line 351-359), extracts the proplib
   command dictionary, and dispatches to `cmd_get_usage_all`,
   `cmd_set_limit`, `cmd_set_limit_uid`, `cmd_set_limit_gid` (lines 380-399)
   — **none** of which call `caps_priv_check`, `priv_check`, `suser`, or any
   other privilege test. Compare `sys/kern/vfs_syscalls.c` where every
   privileged VFS operation calls `caps_priv_check_td(td, ...)`, and the
   UFS quota ioctls in `sys/vfs/ufs/ufs_quota.c` gate on
   `SYSCAP_NOQUOTA_WR` (`sys/sys/caps.h:173`).

3. **Primitive.** The attacker fully controls the proplib command and its
   arguments (uid, gid, limit). The kernel writes attacker-chosen 64-bit
   values directly into `mp->mnt_acct.ac_limit` and
   `unp->uid_chunk[uid].limit` / `gnp->gid_chunk[gid].limit` under the
   mount's spinlock.

4. **Effect (demonstrated):**
   - **Info disclosure:** `"get usage all"` returns every uid/gid's exact
     disk usage and limits. maxx (uid 1001) read root's usage
     (`uid 0: 102400`).
   - **Privilege bypass:** `"set limit uid"` with `uid=0` succeeded
     (rc=0) and the re-read confirmed `uid=0 limit=99999999`.
   - **Filesystem-wide write DoS:** `"set limit"` with a small value
     caused all subsequent writes to return `Disc quota exceeded` —
     maxx wrote 0 bytes after setting the limit, proving the DoS is live.

## This is a logic/auth bug, not memory corruption

No slab grooming, heap feng shui, or escalation chain applies. The bug
**is** the privilege boundary violation itself: the syscall grants
quota-administrator power (set/read all quotas, deny filesystem writes) to
any unprivileged user. The impact ceiling is **filesystem-wide
availability attack + cross-user information disclosure** from any local
unprivileged account. (It does not grant uid=0.)

## PoC

- `df0141_poc.c` — calls `vquotactl(2)` (syscall 530) via the libc
  weak alias, using the same proplib wire format as `sbin/vquota/vquota.c`.
  Exercises read-all, set-uid-limit (targeting uid 0), and set-fs-limit.
- Build: `cc -o df0141_poc df0141_poc.c -lprop`
- Run (unprivileged): `./df0141_poc /tmp` (requires `vfs.quota_enabled=1`)
- Admin setup (one-time): `echo 'vfs.quota_enabled=1' >> /boot/loader.conf`
  then reboot. This enables per-mount accounting on tmpfs/ufs mounts.

## Evidence (before / after)

### Unpatched kernel (#0, `vfs.quota_enabled=1`), as maxx uid=1001:
```
[1] get usage all    rc=0 (SUCCESS) — read root's usage (info disclosure)
[2] set limit uid=0  rc=0 (SUCCESS — PRIVILEGE BYPASS)
[3] re-read          uid=0  limit=99999999  (write confirmed)
[4] set limit /tmp   rc=0 (SUCCESS — FILESYSTEM-WIDE WRITE DoS)
DoS demo: dd → "Disc quota exceeded", 0 bytes written
```

### Patched kernel (#1, fix applied), as maxx uid=1001:
```
vquotactl returned -1, errno=1 (Operation not permitted)
[1]-[4] all rc=-1 (EPERM)
DoS demo: dd → 1024 bytes written (DD_RC=0, NOT blocked)
```

### Root regression (patched kernel):
```
vquota show /tmp   → total: 0          (works)
vquota limit /tmp 500000 → rc=0        (works, limit confirmed)
```

## Fix

`fix.diff` adds `#include <sys/caps.h>` and inserts
`error = caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR); if (error)
return (error);` immediately after the `vfs_quota_enabled` gate in
`sys_vquotactl`. This mirrors the UFS quota ioctl privilege model
(`SYSCAP_NOQUOTA_WR`). The single-fix kernel was built
(`make -j6 nativekernel KERNCONF=X86_64_GENERIC`, rc=0), installed, booted
(kern.version `#1`), and confirmed: maxx gets EPERM on all paths; root
still manages quotas normally.
