tmpfs_chtimes applies va_atime/va_mtime with no ownership/VA_UTIMES_NULL check β non-owner timestamp forgery (DF-3001 contract analog, locally reachable)
| Field | Value |
|---|---|
| ID | DF-3024 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-863 |
| File | sys/vfs/tmpfs/tmpfs_vnops.c |
| Lines | sink tmpfs_subr.c:1291-1325 |
| Area | vfs/tmpfs |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:vfs |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
tmpfs_chtimes applies timestamps after only ROFS/IMMUTABLE/APPEND checks β no ownership or privilege check, and it ignores va_vaflags & VA_UTIMES_NULL. The VFS layer deliberately defers the POSIX rule to the FS: kern_utimensat/kern_futimens gate only on NLC_OWN|NLC_WRITE, and naccess_lva admits root, the owner, OR anyone with write permission β while UFS, ext2, msdosfs, hpfs, smbfs all additionally enforce 'explicit (non-current) timestamps are owner-only; a mere writer may only set current time'. VERIFIED live: unprivileged user forged mtime/atime = 1000000000 (2001-09-09) on a root-owned 0666 file in the default tmpfs /tmp β utimensat returned 0; UFS returns EPERM for the identical operation. NFS SETATTR (tmpfs is exportable via tmpfs_mountctl MOUNTCTL_SET_EXPORT) reaches the same unguarded code with no NLC gate at all. Defeats timestamp-based tamper detection, incident-response ordering, make/rsync/tar freshness decisions. Fix: mirror the UFS check (row diff in pack).
Timeline
- 2026-09-02 Discovered during pass-2 audit of tmpfs_vnops.c (GLM 5.3); forgery reproduced on default /tmp.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3024 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 921 B | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| trigger.c | β | 2.1 KB | view raw | |
| fix.diff | β | 898 B | view raw | |
| run.log | β | 184 B | view raw | |
| env.txt | β | 302 B | view raw | |
| manifest.json | β | 800 B | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-3024 β tmpfs utimes timestamp forgery by non-owner (VFS-contract divergence)
- unprivileged local user (write access, not owner)
- default-mounted tmpfs (
/tmp,/var/run/shm) utimensat()with explicit old timestamps on a root-owned 0666 file β returns 0, timestamps forged (POSIX: EPERM; UFS enforces EPERM)
Build & run
# guest, as root: touch /tmp/df3024_victim && chmod 666 /tmp/df3024_victim # guest, as unprivileged user: cc -O2 -o df3024 trigger.c && ./df3024
Expected output (see run.log):
before : uid=0 mode=0666 mtime=1788606959 utimensat = 0 after : mtime=1000000000 atime=1000000000 uid=0 SUCCESS-CRITERION: forged mtime==1000000000 by non-owner -> FORGERY REPRODUCED
Fix proposal: fix.diff (mirrors ufs_vnops.c:461-470 ownership +
VA_UTIMES_NULL gating in tmpfs_chtimes). Not kernel-validated (Low
severity; fix validation was spent on DF-3023).
DF-3024 β tmpfs_chtimes applies utimes with no ownership check (VFS-contract divergence)
What
tmpfs_chtimes() (sys/vfs/tmpfs/tmpfs_subr.c:1291-1325) applies
va_atime/va_mtime to the node after only three checks: read-only
mount, IMMUTABLE/APPEND flags, and lock state. It performs no
ownership or privilege check and ignores va_vaflags & VA_UTIMES_NULL.
Every other local filesystem enforces the POSIX rule at exactly this
point β e.g. UFS (sys/vfs/ufs/ufs_vnops.c:461-470):
if (cred->cr_uid != ip->i_uid &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_SETATTR)) &&
((vap->va_vaflags & VA_UTIMES_NULL) == 0 ||
(error = VOP_EACCESS(vp, VWRITE, cred))))
return (error);
(same pattern in ext2fs:349, msdosfs:405, hpfs:535, smbfs:359).
Why the VFS layer does not save tmpfs
kern_utimensat (sys/kern/vfs_syscalls.c:3961) gates with
NLC_OWN | NLC_WRITE, and naccess_lva() (sys/kern/vfs_nlookup.c:1869-1925)
admits root, the owner, OR anyone with write permission (group/world
S_IWUSR-shifted mode check at :1910-1924). The stricter POSIX rule β
forging explicit (non-current) timestamps is owner-only; a mere writer
may only set the current time (VA_UTIMES_NULL) β is deliberately
deferred to the filesystem's chtimes. tmpfs never does it.
kern_futimens (:3847) uses the same NLC_OWN | NLC_WRITE gate, so an
fd opened for write (or opened read-only? no β naccess requires write
mode bits) on a non-owned file also reaches VOP_SETATTR unguarded.
The NFS path (nfs_setattr β VOP_SETATTR) performs no NLC checks at
all; tmpfs supports export (tmpfs_mountctl, MOUNTCTL_SET_EXPORT,
tmpfs_vnops.c:1889-1896), so a remote SETATTR can forge timestamps on
any file in an exported tmpfs regardless of mode bits β the exact shape
of DF-3001 (hammer1).
Reproduced
Guest DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC, default tmpfs /tmp.
- root:
touch /tmp/df3024_victim && chmod 666 /tmp/df3024_victim(file ownedroot:wheel, mode 0666, mtime = now). - unprivileged user
maxx(uid 1001, not the owner, write access only) runs./df3024βutimensat(AT_FDCWD, "/tmp/df3024_victim", {mtime=atime=1000000000}, 0). - Result: returns 0; mtime and atime become 1000000000 (2001-09-09) on the root-owned file. POSIX requires EPERM here (non-owner, explicit non-NULL timestamps); UFS returns EPERM for the identical operation via its :464 check.
before : uid=0 mode=0666 mtime=1788606959 utimensat = 0 after : mtime=1000000000 atime=1000000000 uid=0 SUCCESS-CRITERION: forged mtime==1000000000 by non-owner -> FORGERY REPRODUCED
Impact
Integrity of timestamp-based logic on the world's most exposed
directories β /tmp, /var/run, /dev/shm-style mounts: defeat
tamper-evidence/incident-response ordering, make/rsync/tar
freshness decisions, tmpwatch-style cleanup. No memory-safety impact.
Low severity (matches DF-3001), but note it is reachable locally on
default mounts (DF-3001's hammer1 case needed an NFS export).
Fix
Mirror the UFS check in tmpfs_chtimes (sys/vfs/tmpfs/tmpfs_subr.c),
using the node's uid and the caller's cred:
if (vap... /* in chtimes signature: vaflags */)
...
if (cred->cr_uid != node->tn_uid &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_SETATTR)) != 0 &&
((vaflags & VA_UTIMES_NULL) == 0 ||
(error = tmpfs_access_write(vp, cred)) != 0))
return (error);
(concretely: add VA_UTIMES_NULL gating with priv_check/caps_priv_check
and a VOP_EACCESS-style write test β see fix.diff.)
Fix verification
not_testablefix.diff authored (mirrors UFS check) but not kernel-validated; Low-severity finding, verification budget allocated to DF-3023's mandatory fix validation
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
["run.log β root-owned 0666 /tmp file's mtime/atime forged to 1000000000 by non-owner via utimensat (returns 0)", 'trigger.c β minimal reproducer', 'env.txt β guest default tmpfs mounts (/tmp, /var/run/shm), unpriv uid']
PoC changes
none - seed sketch compiled and ran as-is once DFbsd header set (sys/sysctl.h, sys/mount.h) was corrected
Verified recommended fix
Mirror ufs_vnops.c:461-470 in tmpfs_chtimes: owner-or-caps_priv_check(SYSCAP_NOVFS_SETATTR), else require VA_UTIMES_NULL plus effective write access (fix.diff in pack; not kernel-rebuilt - fix budget spent on DF-3023)
Verdict
tmpfs_chtimes (tmpfs_subr.c:1291) applies va_atime/va_mtime with no ownership/privilege check and ignores VA_UTIMES_NULL, while the VFS layer (kern_utimensat:3961, kern_futimens:3847, naccess_lva) deliberately admits root, the owner, OR anyone with write access and defers the stricter POSIX rule (explicit-timestamp forging is owner-only) to the filesystem - a rule every other local FS enforces (ufs_vnops.c:461-470, ext2:349, msdosfs:405, hpfs:535). Demonstrated live: unprivileged user maxx forged mtime/atime = 1000000000 (2001-09-09) on a root-owned 0666 file in the default tmpfs /tmp via utimensat; POSIX/UFS return EPERM for this. NFS SETATTR (tmpfs is exportable via tmpfs_mountctl MOUNTCTL_SET_EXPORT) reaches the same unguarded code regardless of write access - the DF-3001 (hammer1) pattern, here reachable locally on default mounts. Integrity-only impact: defeats timestamp-based tamper detection, make/rsync/tar freshness, tmpwatch semantics on /tmp and /var/run/shm. No memory-safety impact.
No comments yet.