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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3024 Β· 8 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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.

  1. root: touch /tmp/df3024_victim && chmod 666 /tmp/df3024_victim (file owned root:wheel, mode 0666, mtime = now).
  2. unprivileged user maxx (uid 1001, not the owner, write access only) runs ./df3024 β†’ utimensat(AT_FDCWD, "/tmp/df3024_victim", {mtime=atime=1000000000}, 0).
  3. 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_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff authored (mirrors UFS check) but not kernel-validated; Low-severity finding, verification budget allocated to DF-3023's mandatory fix validation

fix.diff
↓ fix.diffper-fix-DF-3024

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.