⬢ DragonFlyBSD Kernel Audit
DF-5842 / timeout_rig.c
← back to finding ↓ download raw
/*
 * DF-5842 rig: select(2)/pselect(2)/ppoll(2) accept invalid (negative /
 * unnormalized) timeout values and feed them unvalidated into
 * kern_kevent()->tstohz_high()/precise_sleep().
 *
 * sys/kern/sys_generic.c:
 *   sys_select()  : copyin timeval, TIMEVAL_TO_TIMESPEC, no range check
 *   sys_pselect() : copyin timespec, no range check
 *   sys_ppoll()   : copyin timespec, no range check
 * (contrast: the nd==0/nfds==0 path goes to nanosleep1() ->
 *  clock_nanosleep1() which DOES reject tv_sec<0 / tv_nsec<0 /
 *  tv_nsec>=1e9 -> EINVAL; kern_time.c:467)
 *
 * Demonstrated misbehavior:
 *   T1: pselect with tv_sec = -1            -> immediate rv=0 (POSIX: EINVAL)
 *   T2: ppoll  with tv_nsec = 2^62          -> immediate rv=0 (POSIX: EINVAL;
 *         intended sleep ~146 years collapses; int overflow in the PRECISE
 *         ustimeout math at kern_event.c:975 or tick wrap in tstohz_high)
 *   T3: select with tv_usec = -5            -> immediate rv=0
 *   T4: control  ppoll {2,0} on idle pipe   -> blocks ~2s (proves rig works)
 *   T5: control  ppoll {0,500000000}        -> blocks ~0.5s
 *   T6: select(0 fds, {tv_sec=-1})          -> dotimeout_only -> nanosleep1
 *         -> EINVAL (proves the nd==0 path validates, nd>0 does not)
 */
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

static void
dummy(int sig __unused)
{
}

static double
now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (ts.tv_sec + ts.tv_nsec / 1e9);
}

int
main(void)
{
	struct pollfd pfd[1];
	struct timespec ts;
	struct timeval tv;
	fd_set rfds;
	int p[2], rc;
	double t0, dt;

	if (pipe(p) < 0) {
		perror("pipe");
		return (1);
	}
	signal(SIGALRM, dummy);
	alarm(30);		/* bound any test that fails to collapse */
	FD_ZERO(&rfds);

	/* T1: pselect tv_sec=-1 -> expect immediate 0 instead of EINVAL */
	FD_SET(p[0], &rfds);
	ts.tv_sec = -1;
	ts.tv_nsec = 0;
	t0 = now();
	rc = pselect(p[0] + 1, &rfds, NULL, NULL, &ts, NULL);
	dt = now() - t0;
	printf("T1 pselect{ -1s }        rc=%d errno=%d (%s) elapsed=%.6f\n",
	    rc, errno, errno ? strerror(errno) : "-", dt);

	/* T2: ppoll tv_nsec = 2^62 -> expect immediate 0 (timer collapse) */
	pfd[0].fd = p[0];
	pfd[0].events = POLLIN;
	ts.tv_sec = 0;
	ts.tv_nsec = 4611686018427387904LL;	/* 2^62 */
	t0 = now();
	rc = ppoll(pfd, 1, &ts, NULL);
	dt = now() - t0;
	printf("T2 ppoll{ 0s, 2^62ns }   rc=%d errno=%d (%s) elapsed=%.6f "
	    "revents=%04x\n", rc, errno, errno ? strerror(errno) : "-", dt,
	    pfd[0].revents);

	/* T2b: ppoll tv_sec = -1 -> expect immediate 0 */
	ts.tv_sec = -1;
	ts.tv_nsec = 0;
	t0 = now();
	rc = ppoll(pfd, 1, &ts, NULL);
	dt = now() - t0;
	printf("T2b ppoll{ -1s }         rc=%d errno=%d elapsed=%.6f\n",
	    rc, errno, dt);

	/* T3: select tv_usec = -5 -> expect immediate 0 */
	FD_SET(p[0], &rfds);
	tv.tv_sec = 0;
	tv.tv_usec = -5;
	t0 = now();
	rc = select(p[0] + 1, &rfds, NULL, NULL, &tv);
	dt = now() - t0;
	printf("T3 select{ 0s, -5us }    rc=%d errno=%d (%s) elapsed=%.6f\n",
	    rc, errno, errno ? strerror(errno) : "-", dt);

	/* T4: control ppoll {2,0} -> ~2s */
	ts.tv_sec = 2;
	ts.tv_nsec = 0;
	t0 = now();
	rc = ppoll(pfd, 1, &ts, NULL);
	dt = now() - t0;
	printf("T4 control ppoll{ 2s }   rc=%d elapsed=%.6f\n", rc, dt);

	/* T5: control ppoll {0,500ms} -> ~0.5s */
	ts.tv_sec = 0;
	ts.tv_nsec = 500000000;
	t0 = now();
	rc = ppoll(pfd, 1, &ts, NULL);
	dt = now() - t0;
	printf("T5 control ppoll{ .5s }  rc=%d elapsed=%.6f\n", rc, dt);

	/* T6: select(nd=0, invalid ts) -> nanosleep1 path -> EINVAL expected */
	ts.tv_sec = -1;
	ts.tv_nsec = 0;
	t0 = now();
	rc = select(0, NULL, NULL, NULL, &tv);	/* tv set below */
	tv.tv_sec = -1;
	tv.tv_usec = 0;
	rc = select(0, NULL, NULL, NULL, &tv);
	dt = now() - t0;
	printf("T6 select(0){ -1s }      rc=%d errno=%d (%s) elapsed=%.6f\n",
	    rc, errno, errno ? strerror(errno) : "-", dt);

	/*
	 * T7: unnormalized {0, 1500000us} = 1.5 s intended; POSIX requires
	 * EINVAL (tv_usec >= 1e6). Compare against control T5 (0.5 s).
	 */
	FD_SET(p[0], &rfds);
	tv.tv_sec = 0;
	tv.tv_usec = 1500000;	/* unnormalized 1.5 s */
	t0 = now();
	rc = select(p[0] + 1, &rfds, NULL, NULL, &tv);
	dt = now() - t0;
	printf("T7 select{ 0s, 1.5e6us } rc=%d errno=%d (%s) elapsed=%.6f\n",
	    rc, errno, errno ? strerror(errno) : "-", dt);

	fflush(stdout);
	return (0);
}