DF-5797 / verdict.json
{ "finding_id": "DF-5797", "status": "reproduced", "verdict": "On DragonFly 6.5-DEVELOPMENT (hammer2 root), an unprivileged user (uid 1001) set arbitrary atime/mtime on a root-owned world-writable file via utimensat(2) (both follow and AT_SYMLINK_NOFOLLOW) and futimens(2). Observed forged values: atime=epoch 0, mtime=66666, later both atime/mtime=12345678. Control file without write permission returned EACCES on all paths, proving the active gate is write permission, not ownership. POSIX.1-2017, FreeBSD (PRIV_VFS_UTIMES) and Linux (CAP_FOWNER) all require EPERM for arbitrary timestamps from a non-owner. Root cause: kern_utimensat (vfs_syscalls.c:3961) and kern_futimens (vfs_syscalls.c:3847) gate only on NLC_OWN|NLC_WRITE which naccess_lva evaluates as owner-OR-write and never distinguish arbitrary times from current-time; HAMMER2s setattr, unlike UFS (ufs_vnops.c:462-468), performs no ownership re-check. Impact ceiling: integrity forgery - a user with write access to a file they do not own can tamper content then restore timestamps, defeating rsync/tar incremental backups, make, and forensic triage.", "reproduced": true, "impact": "none", "confidence": "certain", "exploit_chain": "unprivileged user -> write access to non-owned file (group/world write) -> utimensat/futimens with arbitrary {tv_sec,tv_nsec} -> kernel accepts (write-perm gate only) -> file content tamper becomes invisible to timestamp-based backup/audit tooling", "evidence": "run.log/run.2.log: utimensat(arbitrary) -> OK, after utimensat atime=0 mtime=66666, futimens(arbitrary) -> OK ... atime=12345678 mtime=12345678, mode-644 control EACCES; env.txt: hammer2 root filesystem", "kernel_refs": [ "sys/kern/vfs_syscalls.c:3961", "sys/kern/vfs_syscalls.c:3946", "sys/kern/vfs_syscalls.c:3847", "sys/kern/vfs_syscalls.c:3837", "sys/kern/vfs_nlookup.c:1875", "sys/vfs/ufs/ufs_vnops.c:462" ], "poc_changes": "Seed concept only; wrote the rig from scratch: three syscall paths (utimensat follow/nofollow, futimens on O_RDWR fd) with stat() verification of forged values plus a mode-644 negative control that isolates the write-permission gate.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 95, "guest_dirty": 1, "build_cmd": "cc -O -o /tmp/utimes_forge /tmp/utimes_forge.c", "run_cmd": "sh /tmp/setup_root.sh; /tmp/utimes_forge /tmp/df5797_target (as uid 1001)", "code_hash": "e350aa51067b75c8872a05d1152a2fdd8d9b84ade960a03482cbc7c4f06c8af6", "notes": "No memory corruption; deterministic policy/integrity violation. UFS already enforces POSIX at the VOP layer, HAMMER2 does not - the syscall-layer fix (fix.diff) repairs every filesystem and both entry points. Recommend also adding the ownership re-check to hammer2_setattr as defense in depth.", "recommended_fix": "In kern_utimensat()/kern_futimens(), when the caller-supplied times are not both current-time (nullflag==0), require file ownership or caps_priv_check(SYSCAP_NOVFS_SETATTR), else EPERM - see fix.diff", "fix_status": "not_tested", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff authored and verified to apply cleanly (git apply --check) against sys/kern/vfs_syscalls.c; kernel rebuild validation not performed because this is a permission-policy finding, not reproduced memory corruption (mandatory rebuild rule does not apply). The patched gate directly converts the observed OK results into EPERM while leaving both-current-time requests working.", "fix_evidence": "findings/poc/DF-5797/fix.diff (applies cleanly: git apply --check passed); VERDICT.md Fix validation section" } |