⬢ DragonFlyBSD Kernel Audit
DF-5607 / cotdfault.c
← back to finding ↓ download raw
/*
 * cotdfault.c -- DF-5607 PoC: fault injection inside a REAL vkernel
 * cothread, proving the signal-discipline gap owned by
 * sys/platform/vkernel64/platform/cothread.c.
 *
 * Mechanism (no sys/ modification needed):
 *
 *  - cothread_thread() (cothread.c:138-151) runs cpu_mask_all_signals()
 *    [cothread.c:143] then tls_set_gs(0, ...) which sets the thread's %gs
 *    base to NULL [cothread.c:149]. Every cothread therefore runs with
 *    %gs base == NULL (readable via the get_tls_area(2) syscall, nr 473).
 *
 *  - cpu_mask_all_signals() (machintr.c:205-211) blocks only
 *    SIGALRM|SIGIO|SIGQUIT|SIGUSR1|SIGTERM|SIGWINCH|SIGUSR2|SIGURG --
 *    the synchronous fault signals (SIGSEGV/SIGBUS/SIGTRAP/SIGFPE)
 *    stay DELIVERABLE in every cothread.
 *
 *  - The vkernel installs exc_segfault for those fault signals
 *    process-wide with SA_SIGINFO|SA_NODEFER and an EMPTY sa_mask
 *    (exception.c init_exceptions, exception.c:227-233). kern_trap()'s
 *    first statement is the %gs-relative mycpu load
 *    (thread.h:_get_mycpu "movq %gs:globaldata") -> faults when
 *    %gs==0 -> SA_NODEFER re-enters the handler on the same stack ->
 *    unbounded recursion until the host kills the vkernel: no panic,
 *    no dump, no diagnostic (the DF-5587 dynamic, exercised here on a
 *    real boot instead of a standalone model).
 *
 * LD_PRELOAD this shim into the vkernel binary.
 *
 *   - We interpose sigblock() purely for diagnostics: it lets us report
 *     every caller's %gs base at the exact cpu_mask_all_signals()
 *     calls (cothread.c:109 and cothread.c:143).
 *
 *   - We interpose nanosleep(): the vktimer cothread calls it via
 *     cothread_sleep() (cothread.c:186-189) immediately after
 *     tls_set_gs(0) -- a %gs base of NULL there is BY CONSTRUCTION a
 *     cothread in its real work loop (vktimer_thread, systimer.c:244).
 *     The first time we see that, we inject the fault a real cothread
 *     bug would take (e.g. if_vke.c's deliberate "*(volatile int *)0=1"
 *     NULL-ring write): pthread_kill(self, SIGSEGV).
 *
 * If the discipline were correct (fault signals blocked for cothreads,
 * or a re-entrancy-safe fault handler), the vkernel would survive or
 * die loudly. Baseline stock vkernel: silent unbounded recursion death.
 *
 * Build (guest, as root):  cc -shared -O2 -fPIC -o cotdfault.so cotdfault.c
 * Run:   LD_PRELOAD=/root/cotdfault.so ./kernel.debug ...
 */

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <dlfcn.h>

struct tls_info {
	void *base;
	long size;
};

static int seen_nonnull_gs;	/* some thread ran with %gs set (a cpu) */
static int diag_count;		/* cap diagnostic lines */

static const char msg_cotd[] =
    "COTDFAULT: nanosleep() in cothread context (vktimer_thread, %gs base=NULL "
    "per tls_set_gs(0) cothread.c:149)\n";
static const char msg_fire[] =
    "COTDFAULT: injecting SIGSEGV -- fault signals are NOT blocked by "
    "cpu_mask_all_signals (machintr.c:205) in this cothread\n";

static int
gs_base_is_null(void)
{
	struct tls_info info;

	memset(&info, 0, sizeof(info));
	if (syscall(473, 1, &info, sizeof(info)) != 0)
		return (0);		/* be conservative */
	return (info.base == NULL);
}

int
sigblock(int mask)
{
	static int (*real_sigblock)(int);

	if (real_sigblock == NULL)
		real_sigblock = dlsym(RTLD_NEXT, "sigblock");

	if (diag_count < 40) {
		++diag_count;
		if (gs_base_is_null()) {
			write(2, "COTDFATAL-DIAG: sigblock() caller %gs base=NULL "
			    "(cothread_thread cothread.c:143)\n", 71);
		} else {
			seen_nonnull_gs = 1;
		}
	} else if (!gs_base_is_null()) {
		seen_nonnull_gs = 1;
	}

	return real_sigblock(mask);
}

int
nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
{
	static int (*real_nanosleep)(const struct timespec *,
	    struct timespec *);

	if (real_nanosleep == NULL)
		real_nanosleep = dlsym(RTLD_NEXT, "nanosleep");

	if (seen_nonnull_gs && gs_base_is_null()) {
		static int injected;
		if (!injected) {
			injected = 1;
			write(2, msg_cotd, sizeof(msg_cotd) - 1);
			write(2, msg_fire, sizeof(msg_fire) - 1);
			pthread_kill(pthread_self(), SIGSEGV);
			/* if we return, a fixed kernel survived the fault */
		}
	} else if (!seen_nonnull_gs && !gs_base_is_null()) {
		seen_nonnull_gs = 1;
	}

	return real_nanosleep(rqtp, rmtp);
}