tmpfs usermount memory cap is attacker-declared: size_max is never clamped to pages_limit for non-root mounts (vfs.usermount=1 β unbounded tm_pages_max/tm_nodes_max)
Summary
tmpfs_mount computes the machine cap pages_limit = vm_swap_max + v_page_count/2 (:157) but only applies it (halved) to the size_max==0 default; the explicit-size path assigns pages = OFF_TO_IDX(size_max) in BOTH branches (:163-173 - identical code, the comment admits the clamp was dropped for root fstab boot-order reasons). Usermounts are not distinguished: with vfs.usermount=1 any user mounting on a self-owned directory (SYSCAP_NOMOUNT_TMPFS under __SYSCAP_NOROOTTEST, default creds unrestricted) sets tm_pages_max up to 2^51 pages and the derived tm_nodes_max likewise, so the ENOSPC guard in tmpfs_reg_resize never fires and the only backstop is machine-wide memory exhaustion. VERIFIED: uid 1001 mount with ta_size_max=1TiB reported f_blocks=268435456 pages (1024 GiB) vs the ~3GiB machine cap on the 4GB/4GB guest (341x). Unpriv whole-system memory-exhaustion DoS under vfs.usermount=1 with a single mount syscall + file writes; also feeds the unbounded-node variants of DF-3179. Fix: clamp pages to pages_limit for cred->cr_uid != 0.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3182 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| build.log | β | 65 B | view raw | |
| build.sh | β | 68 B | view raw | |
| env.txt | β | 419 B | view raw | |
| run.log | β | 1.1 KB | view raw | |
| run.sh | β | 91 B | view raw | |
| umount_demo.c | β | 3.3 KB | view raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-3182 β tmpfs usermount memory cap is attacker-declared: size_max is never clamped to pages_limit
What
tmpfs_mount computes a system cap
pages_limit = vm_swap_max + vmstats.v_page_count / 2
(sys/vfs/tmpfs/tmpfs_vfsops.c:157) but then applies it only to the default
(size_max == 0) case, halved. For any explicit size_max the mount takes
the attacker's value verbatim β the OFF_TO_IDX(size_max) > pages_limit
branch is deliberately unclamped (comment at :164-169: fstab boot-order
concerns), up to OFF_TO_IDX(INT64_MAX) = 2^51 pages:
} else if (OFF_TO_IDX(size_max) > pages_limit) {
/* do not force pages = pages_limit ... */
pages = OFF_TO_IDX(size_max); /* no clamp! */
} else {
pages = OFF_TO_IDX(size_max); /* identical branch */
}
There is no distinction between root mounts (where the fstab rationale may
apply) and non-root usermounts: with vfs.usermount=1, any user mounting a
tmpfs on a directory they own sets tm_pages_max (and the derived
tm_nodes_max) arbitrarily (sys/kern/vfs_syscalls.c:151-155 β
SYSCAP_NOMOUNT_TMPFS under __SYSCAP_NOROOTTEST, default creds are
unrestricted). The ENOSPC machinery in tmpfs_reg_resize
(tmpfs_subr.c:1007-1011) then compares against the attacker's number and
never fires β the only backstop is real machine-wide memory exhaustion.
Reproduce (observed on the guest, unprivileged user maxx, vfs.usermount=1)
# ./umount_demo /home/maxx/dfm1 mounter uid=1001 euid=1001, spoofing ta_root_uid/gid=0, ta_size_max=1TiB statfs: f_blocks=268435456 (x PAGE_SIZE = 1099511627776 bytes = 1024 GiB), ...
The designed cap on this guest (4 GB RAM, 4 GB swap) would be
(1048576 + 520k)/2 β 784k pages β 3 GiB; the user-mounted tmpfs reports
268,435,456 pages (1 TiB), i.e. 341x past the intended bound. Writing past
3 GiB into such a mount succeeds (no ENOSPC) until the machine exhausts
memory β unprivileged whole-system DoS (swap thrash/OOM), by design of the
missing clamp.
Fix
For non-root mounters (cred->cr_uid != 0), clamp:
if (pages > pages_limit) pages = pages_limit; (the boot-order rationale
in the comment concerns root fstab mounts only). Optionally also enforce a
global sum across user-mounted tmpfses.
DF-3182 verdict β REPRODUCED (mount-side limit derivation accepts attacker-declared size for usermounts)
Guest: DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC (INVARIANTS), 4.25 GB RAM, 4 GB swap, vfs.usermount=1 (set as root for the demo; it is the documented precondition for unprivileged mounts β default is 0).
As unprivileged user maxx (uid 1001), mounting a tmpfs on a self-owned directory with ta_size_max = 1 TiB:
statfs: f_blocks=268435456 (x PAGE_SIZE = 1099511627776 bytes = 1024 GiB)
The kernel-derived system cap for this machine would be
pages_limit = vm_swap_max + v_page_count/2 = 1,568,895 pages, halved for
defaults = 784,447 pages (~3 GiB). The user's mount stores
tm_pages_max = 268,435,456 pages β 341x the intended bound β because
tmpfs_vfsops.c:163-173 assigns pages = OFF_TO_IDX(size_max) in BOTH the
over-limit and normal branches (the comment admits the clamp was dropped
for boot-time fstab ordering; usermounts were not considered).
Consequence: the ENOSPC guard in tmpfs_reg_resize (tmpfs_subr.c:1007-1011) compares against the attacker's number and cannot fire; filling such a mount degrades only at whole-machine memory exhaustion (swap thrash/OOM) β an unprivileged system-wide DoS under vfs.usermount=1. The actual OOM wedge was not executed on the single-tenant guest to keep it usable for the other verifications; the bypass itself (f_blocks + successful writes into the mount beyond any kernel-derived cap) is captured in run.log.
Note: even without this bug, per-mount caps are per-mount (a user could stack several clamped mounts); the bug reduces the cost to one syscall and one mount and removes the kernel's own notion of a bound entirely. Rated Low-to-Medium accordingly (conditional on vfs.usermount=1).
Status: reproduced / impact=dos (limit bypass demonstrated; OOM consequence immediate) / confidence certain for the derivation, likely for the machine-wide impact.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testable[]
Confirmed kernel references
Detail
Evidence (decisive lines)
['run.log (f_blocks=268435456 = 1TiB from uid 1001 mount)', 'umount_demo.c', 'VERDICT.md']
PoC changes
sys/statfs.h include removed (DF: statfs in sys/mount.h); struct tmpfs_mount_info mirrored locally
Verified recommended fix
clamp pages to pages_limit for non-root mounters (cred->cr_uid != 0)
Verdict
REPRODUCED (derivation bypass): with vfs.usermount=1, unprivileged user maxx (uid 1001) mounted a tmpfs with ta_size_max=1TiB; statfs reports f_blocks=268435456 pages (1024 GiB) vs the kernel-derived machine cap of ~784,447 pages (~3GiB) β tmpfs_vfsops.c:163-173 assigns pages=OFF_TO_IDX(size_max) with no clamp in the over-limit branch (identical code in both branches; the comment admits the clamp was dropped for root fstab boot-order reasons, and usermounts were not considered). tm_pages_max (and derived tm_nodes_max) are therefore attacker-declared; the ENOSPC guard in tmpfs_reg_resize (tmpfs_subr.c:1007) cannot fire and the only backstop is machine-wide memory exhaustion. The OOM wedge itself was not executed to preserve the single-tenant guest; the bypass is captured in run.log.
No comments yet.