# 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`):

```c
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:

```c
	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`.)
