β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5684

sys_vquotactl() performs no privilege check β€” unprivileged users set/wipe quota limits for any uid/gid/mount, denying ALL writes (including root's) filesystem-wide

Summary

No priv_check anywhere in the dispatch: with the feature enabled, accounting is installed on every filesystem and vop_write consults vq_write_ok for every write returning EDQUOT with NO root bypass. Reproduced as uid 1001: 'vquota limit / 1' then root's own appends fail EDQUOT (password changes, logging, cron all break until the attacker lifts it); per-uid usage of all users also readable. Medium only because vfs.quota_enabled is default-off (High when enabled). caps_priv_check_td(SYSCAP_NOQUOTA_WR) fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5684 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
build.sh β€” 101 B view raw
env.txt β€” 225 B view raw
fix.diff β€” 500 B view raw
run.log β€” 1.0 KB view raw
run.patched.log β€” 662 B view raw
run.sh β€” 155 B view raw
vq_priv.c β€” 887 B view raw
vq_priv.sh β€” 742 B view raw
verdict.json β€” 3.1 KB view raw

DF-5684 β€” sys_vquotactl() has no privilege check: unprivileged quota-limit manipulation β†’ system-wide write DoS

What

sys_vquotactl() (sys/kern/vfs_quota.c:328-413) dispatches quota commands with no priv_check of any kind:

  • set limit (vfs_quota.c:273-284) β€” sets mount-wide ac_limit
  • set limit uid (vfs_quota.c:287-305) β€” per-uid limit, any uid
  • set limit gid (vfs_quota.c:308-326) β€” per-gid limit, any gid
  • set usage all (vfs_quota.c:219-270) β€” wipes/rewrites all accounting
  • get usage all (vfs_quota.c:175-216) β€” reads usage of every uid/gid

Enforcement is universal once the feature is enabled: vfs_init.c:431-432 installs vfs_stdac_init on every filesystem, and vop_write (vfs_vopops.c:472-491) consults vq_write_ok() for every regular file write, returning EDQUOT β€” with no privilege bypass for root.

Classic quotactl(2) enforces quota privilege inside the per-FS code (PRFIV_VFS_QUOTA equivalent); vquotactl(2) simply forgot it.

Reproduce (baseline)

Guest booted with vfs.quota_enabled=1; run everything as unprivileged uid 1001 (maxx):

$ echo before >> /root/df5684.txt && echo ROOT_WRITE_OK_BEFORE # as root ROOT_WRITE_OK_BEFORE $ vquota limit / 1 # AS UID 1001: set mount-wide limit = 1 byte setlimit_rc=0 $ vquota show / # AS UID 1001 total: 0, limit = 1 # now as ROOT: $ echo after >> /root/df5684.txt echo: write error on stdout ROOT_WRITE_BLOCKED_RC=2 $ vquota ulim / root 1 # AS UID 1001: target root's uid specifically maxx set uid-root limit=1 rc=0 $ echo rootwrite >> /root/df5684.txt # as root ROOT_APPEND_BLOCKED $ vquota ulim / root 0 && vquota limit / 0 # attacker can also lift it restored $ echo ok >> /root/df5684.txt && echo ROOT_APPEND_OK_AFTER_RESTORE ROOT_APPEND_OK_AFTER_RESTORE

(In-place overwrite with delta=0 still passes β€” vq_write_ok only blocks net growth β€” which is why dd conv=notrunc succeeded while appends failed.)

Expected output (patched kernel, fix.diff applied)

$ vquota limit / 1 # as uid 1001 send_command: vquotactl = -1 Failed to send message to kernel maxx_setlimit_rc=1 # EPERM before any state change $ echo ... >> /root/df5684.txt # as root ROOT_WRITE_OK_ON_PATCHED $ vquota limit / 1 && vquota show / && vquota limit / 0 # as root root_setlimit_rc=0 / total: 0, limit = 1 / root_limit_restored

Files

  • vq_priv.sh unprivileged part (set limit / ulim / show / sync)
  • fix.diff minimal privilege-check fix (git-applyable)
  • run.log baseline capture (maxx sets limits, root blocked)
  • run.patched.log fix validation capture
  • VERDICT.md / manifest.json / verdict.json
VERDICT.md
↓ download raw

DF-5684 VERDICT

Status: REPRODUCED (unprivileged authorization bypass β†’ persistent write DoS for all users incl. root) β€” FIXED by fix.diff (validated on rebuilt kernel)

Environment

Same guest as DF-5683: DragonFly 6.5-DEVELOPMENT x86_64, baseline kernel #0 (Jul 2 2026) / patched kernel #1 (Oct 10 00:36:54 2026), vfs.quota_enabled=1 boot tunable (feature default-off).

Chain (baseline kernel)

  1. uid 1001 maxx calls vquota limit / 1 β†’ sys_vquotactl has no privilege check (vfs_quota.c:328-413) β†’ cmd_set_limit writes mp->mnt_acct.ac_limit = 1 (vfs_quota.c:273-284). rc=0.
  2. Every subsequent regular-file write on the mount by ANY user (root included) goes through vop_write β†’ vq_write_ok (vfs_vopops.c:489): ac_bytes + delta > ac_limit β†’ EDQUOT β†’ write(2) fails.
  3. Demonstrated: root append to /root/df5684.txt fails ("write error"), after having succeeded seconds earlier.
  4. Same for per-uid targeting: vquota ulim / root 1 as maxx β†’ root's own appends blocked (ROOT_APPEND_BLOCKED), lifted by maxx (ulim / root 0).
  5. vquota show / as maxx returns every uid/gid's usage (info disclosure of accounting data); sync (set usage all) wipes accounting.
  6. Effect persists until an attacker-chosen lift or reboot β€” a persistent filesystem-write DoS of the whole root mount, executed by an unprivileged user. No root escape needed for impact; combined with any local setuid-root write (e.g. passwd(1), periodic scripts), it also breaks system maintenance entirely.

Why beyond DF-2231

DF-2231 = memory-safety/DoS of the prop copyin primitive itself. This is the missing-authorization defect of the syscall consuming it β€” the object is parsed fine; the command should never have been executable by uid 1001.

Fix validation

fix.diff adds caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR) right after the vfs_quota_enabled gate (the same capability classic quota operations require), plus the missing if (error) return after the plistref copyin.

Patched kernel #1: - vquota limit / 1 as maxx β†’ "send_command: vquotactl = -1", rc=1 (EPERM); mount state unchanged; root writes unaffected (ROOT_WRITE_OK_ON_PATCHED). - Regression: root can still set/lift limits (root_setlimit_rc=0, limit = 1 observed via vquota show, restored to 0).

kernel_refs

sys/kern/vfs_quota.c:328-413 (no priv_check), :273-284, :287-305, :308-326, :219-270 sys/kern/vfs_vopops.c:472-491 (EDQUOT enforcement, no root bypass) sys/kern/vfs_init.c:431-432 (feature applies to every filesystem) sys/sys/caps.h:173 (SYSCAP_NOQUOTA_WR β€” the capability the fix enforces)

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel #1: 'vquota limit / 1' as uid 1001 returns EPERM (send_command: vquotactl = -1), mount state unchanged, root write succeeds; root set/show/lift regression OK.

run.patched.log
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 10 00:36:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

unpriv vquota limit / 1 -> vq_write_ok() fails for all writers on / (incl. root) -> persistent filesystem-wide write DoS until attacker lifts or reboot; breaks passwd, logging, package ops for every user.

Evidence (decisive lines)

run.log (ROOT_WRITE_OK_BEFORE -> maxx sets limit -> ROOT_WRITE_BLOCKED_RC=2 -> ulim root -> ROOT_APPEND_BLOCKED -> restore -> OK_AFTER_RESTORE), run.patched.log (maxx EPERM, ROOT_WRITE_OK_ON_PATCHED, root set/lift regression)

PoC changes

Guest vquota CLI differs from the checkout (limit/ulim/glim/show/sync, user by NAME not uid); adjusted vq_priv.sh accordingly. vquota exits 1 on success-path 'show' (tool quirk) β€” success judged by output/state, not exit code.

Verified recommended fix

Add caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR) after the vfs_quota_enabled gate

Verdict

REPRODUCED: sys_vquotactl(2) contains no privilege check on any command. Unprivileged uid 1001 set a mount-wide quota limit of 1 byte via base-system vquota(8) ('vquota limit / 1', rc=0); afterwards EVERY regular-file write on the mount fails EDQUOT β€” demonstrated against root (append blocked, lifted only by the attacker). Per-uid targeting of root ('vquota ulim / root 1' as uid 1001 -> ROOT_APPEND_BLOCKED), accounting wipe ('sync') and all-uid usage readout ('show') also unprivileged. Enforcement is universal (vfs_init.c:431 installs accounting on every FS; vfs_vopops.c:489 returns EDQUOT with no root bypass) but the whole feature is behind the default-off vfs.quota_enabled boot tunable. fix.diff (caps_priv_check_td(curthread, SYSCAP_NOQUOTA_WR)) validated on a rebuilt kernel: unpriv attempt -> EPERM, root writes unaffected, privileged set/lift regression-tested OK.