โฌข DragonFlyBSD Kernel Audit
DF-5587 / coresig.c
โ† back to finding โ†“ download raw
/*
 * coresig.c โ€” DF-5587 reproducer (cothread fault -> unbounded handler
 * recursion under the exception.c signal installs, on the DragonFly host).
 *
 * Models the vkernel cothread condition:
 *
 *   - cothread threads run with %gs = 0 (tls_set_gs(0, ...),
 *     cothread.c:143-148) โ€” "mycpu" is ILLEGAL in cothreads;
 *   - cpu_mask_all_signals() masks only the 8 interrupt signals
 *     (machintr.c:205-211) โ€” SIGSEGV/SIGBUS/SIGTRAP/SIGFPE stay
 *     deliverable in cothreads;
 *   - those fault signals are handled process-wide by exc_segfault,
 *     installed SA_SIGINFO|SA_NODEFER with an EMPTY sa_mask
 *     (exception.c:222-229);
 *   - exc_segfault's first real work is kern_trap(), whose first statement
 *     is `struct globaldata *gd = mycpu;` (trap.c:585) โ€” a %gs-relative
 *     load that FAULTS when %gs==0.
 *
 * Consequence: any fault in a cothread -> exc_segfault -> kern_trap ->
 * mycpu fault -> SIGSEGV re-delivered (SA_NODEFER, same stack) -> same
 * fault -> unbounded recursion -> host cannot push the signal frame ->
 * process death with NO vkernel panic, NO dump, NO diagnostic.
 *
 * This reproducer installs the exact signal configuration of
 * init_exceptions() and a handler whose first dereference is of a NULL
 * "TLS" base (the %gs==0 condition), then reports the recursion depth
 * reached before the host killed the child.
 *
 * Expected: child dies of SIGSEGV (recursion), parent prints
 * RECURSION_DEPTH=<large> and SILENT_DEATH_CONFIRMED.
 */

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static volatile long *depthp;          /* shared with parent: SHARED anon */
static volatile long tls_base;         /* the "%gs base": 0, like cothread */

/*
 * ~ exc_segfault() [exception.c:262-272] -> kern_trap() [trap.c:583-585]:
 *   struct globaldata *gd = mycpu;   <- %gs-relative load, %gs==0 -> fault
 */
static void
fault_handler(int sig, siginfo_t *si, void *ctx)
{
	int save = errno;

	(*depthp)++;                    /* count re-entries (mapped SHARED) */

	/* kern_trap's first statement with a cothread's %gs==0: */
	{
		volatile char *p =
		    (volatile char *)(uintptr_t)tls_base;
		*p;                       /* fault at "mycpu" -> re-enter */
	}
	errno = save;
}

int
main(void)
{
	struct sigaction sa;
	pid_t pid;
	int st;

	depthp = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
		      MAP_SHARED|MAP_ANON, -1, 0);
	if (depthp == MAP_FAILED) {
		perror("mmap");
		return (2);
	}
	*depthp = 0;
	tls_base = 0;                   /* cothread %gs base */

	pid = fork();
	if (pid < 0) {
		perror("fork");
		return (2);
	}
	if (pid == 0) {
		/* ~ init_exceptions() [exception.c:222-229] โ€” exact */
		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = fault_handler;
		sa.sa_flags = SA_SIGINFO | SA_NODEFER;
		sigemptyset(&sa.sa_mask);
		sigaction(SIGSEGV, &sa, NULL);

		/* "the cothread takes a fault" (driver bug, bad DMA pointer,
		 * stack overrun โ€” anything): */
		{
			volatile char *p = (volatile char *)(uintptr_t)0x10;
			*p;               /* first SIGSEGV */
		}
		_exit(0);              /* not reached */
	}

	if (waitpid(pid, &st, 0) < 0) {
		perror("waitpid");
		return (2);
	}

	printf("child wait status: exited=%d exit=%d signaled=%d termsig=%d "
	       "core=%d\n",
	       WIFEXITED(st), WIFEXITED(st) ? WEXITSTATUS(st) : -1,
	       WIFSIGNALED(st), WIFSIGNALED(st) ? WTERMSIG(st) : -1,
	       WCOREDUMP(st));
	printf("RECURSION_DEPTH=%ld\n", *depthp);

	if (WIFSIGNALED(st) && *depthp > 100) {
		printf("SILENT_DEATH_CONFIRMED: handler re-entered %ld times "
		       "before the host killed the process โ€” no panic, no "
		       "dump path, no diagnostic\n", *depthp);
		return (0);
	}
	printf("UNEXPECTED_OUTCOME\n");
	return (1);
}