Missing privilege check in sys_vquotactl: any user can set/read all quotas
Summary
sys_vquotactl(syscall 530) has NO caps_priv_check anywhere. UFS quota ioctls check SYSCAP_NOQUOTA_WR. Any unprivileged user can: set ac_limit=0 -> filesystem-wide write DoS; set/remove per-uid/gid limits for arbitrary users including uid 0; get usage all -> cross-user info disclosure. Gated by vfs_quota_enabled=0 default.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0141 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0141_poc.c | trigger-source | unprivileged vquotactl privilege-bypass PoC (read/set all quotas via syscall 530) | 6.7 KB | view raw |
| build.sh | build-script | cc -o df0141_poc df0141_poc.c -lprop | 143 B | view raw |
| run.sh | run-script | runs PoC + DoS end-to-end demo as unprivileged user | 741 B | view raw |
| build.log | build-log | full nativekernel build of the single-fix kernel (rc=0) | 5.6 MB | β download |
| run.log | run-log | baseline run on unpatched #0 kernel: all ops SUCCESS + DoS confirmed | 1.4 KB | view raw |
| fix_run.log | run-log | patched #1 kernel run: all ops EPERM, DoS neutralized | 1.1 KB | view raw |
| fix.diff | suggested-fix | add caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR) to sys_vquotactl | 912 B | view raw |
| env.txt | environment | uname, kern.version, cc version, quota state, kernel sha256 | 619 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, evidence, fix validation | 4.6 KB | β raw |
| README.md | readme | human reproduce doc | 1.4 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0141 β Missing privilege check in sys_vquotactl
Bug: sys/kern/vfs_quota.c:sys_vquotactl (syscall 530) has no
caps_priv_check / privilege test. Any unprivileged local user can read
every user's disk usage, set/remove per-uid/gid quota limits (including
root's), and set the filesystem-wide ac_limit (filesystem-wide write DoS).
Class: privilege boundary violation (logic/auth). Not memory corruption.
Reproduce
Admin precondition (realistic β admin deploying VFS quotas):
echo 'vfs.quota_enabled=1' >> /boot/loader.conf reboot
Build & run as unprivileged user:
./build.sh ./run.sh # defaults to /tmp # or: ./df0141_poc /tmp
Expected (bug present, unpatched kernel): all vquotactl operations
return rc=0 (SUCCESS). maxx reads root's usage, sets root's limit to
99999999 (confirmed by re-read), sets /tmp fs-wide limit. The DoS takes
effect: dd to /tmp returns "Disc quota exceeded".
Expected (fixed kernel): all vquotactl operations return EPERM
(errno=1, Operation not permitted). Writes to /tmp succeed normally.
Files
df0141_poc.cβ the PoC sourcefix.diffβ the one-line fix (addcaps_priv_check_td+ include)run.logβ baseline (unpatched) decisive runfix_run.logβ patched kernel decisive runbuild.logβ full single-fix kernel build logVERDICT.mdβ detailed analysismanifest.jsonβ artifact catalog
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)
-
Reachability gate.
sys_vquotactl(sys/kern/vfs_quota.c:328) begins withif (!vfs_quota_enabled) return EOPNOTSUPP;(line 342).vfs_quota_enabledis a boot tunable (vfs.quota_enabled=1in/boot/loader.conf),CTLFLAG_RDso not settable at runtime. An admin who deploys VFS quotas sets this; it is a realistic deployment precondition, not part of the exploit chain. -
No privilege check. After the gate, the function does
nlookupon the user-supplied path (line 351-359), extracts the proplib command dictionary, and dispatches tocmd_get_usage_all,cmd_set_limit,cmd_set_limit_uid,cmd_set_limit_gid(lines 380-399) β none of which callcaps_priv_check,priv_check,suser, or any other privilege test. Comparesys/kern/vfs_syscalls.cwhere every privileged VFS operation callscaps_priv_check_td(td, ...), and the UFS quota ioctls insys/vfs/ufs/ufs_quota.cgate onSYSCAP_NOQUOTA_WR(sys/sys/caps.h:173). -
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_limitandunp->uid_chunk[uid].limit/gnp->gid_chunk[gid].limitunder the mount's spinlock. -
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"withuid=0succeeded (rc=0) and the re-read confirmeduid=0 limit=99999999. - Filesystem-wide write DoS:"set limit"with a small value caused all subsequent writes to returnDisc 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β callsvquotactl(2)(syscall 530) via the libc weak alias, using the same proplib wire format assbin/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(requiresvfs.quota_enabled=1) - Admin setup (one-time):
echo 'vfs.quota_enabled=1' >> /boot/loader.confthen 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.
Fix verification
fixedVALIDATED: baseline maxx reads/sets root quota + fs DoS; patched EPERM all ops, dd succeeds, root works.
BEFORE: maxx rc=0 all ops, dd blocked. AFTER: maxx EPERM, dd=1024B, root rc=0.
Confirmed kernel references
Detail
Exploit chain
none -- privilege bypass IS the bug. maxx gets quota-admin power: read all users, set any limit, fs-wide write DoS.
Evidence (decisive lines)
BEFORE: maxx get usage rc=0, set limit uid=0 rc=0 (root's quota changed), set fs limit -> dd 'Disc quota exceeded'. AFTER: maxx EPERM all ops, dd succeeds, root unaffected.
PoC changes
Authored: df0141_poc.c (proplib vquotactl syscall), fix.diff (caps_priv_check_td SYSCAP_NOQUOTA_WR after quota_enabled gate), VERDICT.md, manifest.json.
Verified recommended fix
Add caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR) in sys_vquotactl after vfs_quota_enabled gate at vfs_quota.c:342. Mirrors UFS quota ioctl model. Full diff in findings/poc/DF-0141/fix.diff.
Verdict
REPRODUCED. sys_vquotactl (syscall 530) vfs_quota.c:328-413 has NO caps_priv_check. maxx reads root's disk usage, sets root's uid-0 quota limit, sets fs-wide ac_limit -> 'Disc quota exceeded' DoS. Precondition: vfs.quota_enabled=1 boot tunable.
No comments yet.