⬢ DragonFlyBSD Kernel Audit
DF-5800 / stackstress.c
← back to finding ↓ download raw
/*
 * DF pass-3 vm_map.c stress rig — stack-growth vs concurrent map mutation.
 *
 * Exercises:
 *  - vm_map_growstack insert/coalesce/reclip interaction (vm_map.c:4246-4272)
 *  - stale aux.avail_ssize on the original stack entry after middle-segment munmap
 *  - simplify-merge of grown stack segments (eflags==0, id=STACK entries)
 *  - PROT_NONE guard clamp path (vm_map.c:4189-4198)
 *  - RLIMIT_STACK lowering via setrlimit -> kern_plimit vm_map_protect(PROT_NONE)
 *
 * build: cc -O -o stackstress stackstress.c -lpthread
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

volatile sig_atomic_t stop_flag;
volatile unsigned long faults_ok, faults_bad;
extern char *__progname;

#ifndef VM_INHERIT_NONE
#define VM_INHERIT_SHARE	0
#define VM_INHERIT_COPY		1
#define VM_INHERIT_NONE		2
#endif

#define USRSTACK_TOP	0x7ffffffff000UL

static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	/* recover: longjmp-lite via pthread_exit is unsafe; just record and die */
 faults_bad++;
	_exit(10 + (sig & 7));
}

/* grow the stack downward by touching memory below the current SP */
static void __attribute__((noinline))
grow_stack(unsigned char *probe)
{
	/* volatile to force the actual memory touch */
	volatile unsigned char sink;
	sink = *probe;
	(void)sink;
}

static void *
mutator(void *arg)
{
	unsigned long region = (unsigned long)arg; /* base of reservation probe area */
	int i = 0;

	while (!stop_flag) {
		void *p = (void *)(region - ((unsigned long)(random() % 64) + 1) * 4096);
		switch (i % 6) {
		case 0:
			/* mprotect inside/below the current stack bottom */
			mprotect(p, 4096 * (1 + random() % 4),
				 PROT_READ | PROT_WRITE | PROT_EXEC);
			break;
		case 1:
			mprotect(p, 4096, PROT_NONE);
			break;
		case 2:
			mprotect(p, 4096, PROT_READ | PROT_WRITE);
			break;
		case 3:
			madvise(p, 4096 * 4, MADV_NORMAL);
			break;
		case 4:
			madvise(p, 4096, MADV_DONTNEED);
			break;
		case 5:
			/* minherit NONE on random subranges (fork gaps) */
			minherit(p, 4096, VM_INHERIT_NONE);
			break;
		}
		i++;
		usleep(100);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	struct rlimit rl;
	pthread_t th;
	unsigned char *probe;
	stack_t altss;
	char altstack[64 * 1024];
	int round;
	pid_t pid;
	int status;

	/* alternate signal stack so a SIGSEGV on a broken stack still runs */
	altss.ss_sp = altstack;
	altss.ss_size = sizeof(altstack);
	altss.ss_flags = 0;
	sigaltstack(&altss, NULL);

	if (argc > 1 && !strcmp(argv[1], "child")) {
		/*
		 * Child: get current stack usage estimate and probe downward
		 * while a mutator beats on the reservation region.
		 */
		unsigned char dummy;
		unsigned long sp = (unsigned long)&dummy;
		unsigned long floor_;

		srandom(getpid());
		floor_ = (sp - (8UL << 20)) & ~4095UL; /* probe 8MB down */

		/*
		 * Spawn mutator that beats on the reservation region just
		 * below where we'll grow.  The reservation is 512MB
		 * (maxssiz) below USRSTACK.
		 */
		if (pthread_create(&th, NULL, mutator,
				   (void *)((sp - (1UL << 20)) & ~4095UL)))
			perror("pthread_create");

		for (round = 0; round < 200 && !stop_flag; round++) {
			for (probe = (unsigned char *)(sp & ~4095UL);
			     (unsigned long)probe > floor_;
			     probe -= 4096) {
				grow_stack(probe);
				faults_ok++;
			}
			/*
			 * Now unmap a chunk of what we just grew (stale
			 * avail_ssize path), then grow again next round.
			 */
			munmap((void *)(floor_ & ~4095UL), 1UL << 20);
		}
		stop_flag = 1;
		pthread_join(th, NULL);
		printf("child done: %lu touches ok\n", faults_ok);
		_exit(0);
	}

	if (argc > 1 && !strcmp(argv[1], "guard")) {
		/*
		 * Guard harness: place a PROT_NONE guard page a few hundred
		 * KB below the current stack bottom, then try to grow past
		 * it.  Expect SIGSEGV at the guard (kernel must NOT map
		 * memory beyond the guard).
		 */
		unsigned char dummy;
		unsigned long sp = (unsigned long)&dummy;
		unsigned long guard = (sp - (2UL << 20)) & ~4095UL;
		void *g = mmap((void *)guard, 4096, PROT_NONE,
			       MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (g == MAP_FAILED) {
			perror("mmap guard");
			_exit(1);
		}
		/* grow down toward the guard */
		for (probe = (unsigned char *)((sp & ~4095UL)); ;
		     probe -= 4096) {
			if ((unsigned long)probe < guard - (64UL << 10)) {
				printf("GUARD BYPASSED: mapped below guard "
				       "at %p\n", probe);
				_exit(2);
			}
			grow_stack(probe);
			if ((unsigned long)probe <= guard + 4096)
				break;
		}
		/* Now fault AT the guard: must die with SIGSEGV */
		printf("reached guard; faulting into it (expect SIGSEGV)\n");
		fflush(stdout);
		grow_stack((unsigned char *)guard);
		printf("GUARD BYPASSED: guard page READABLE at %p\n",
		       (unsigned char *)guard);
		_exit(3);
	}

	/*
	 * Parent: repeatedly fork children that stress-grow their stacks,
	 * sometimes lowering RLIMIT_STACK mid-flight.
	 */
	for (round = 0; round < 40; round++) {
		pid = fork();
		if (pid == 0) {
			struct sigaction sa;
			memset(&sa, 0, sizeof(sa));
			sa.sa_sigaction = segv_handler;
			sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
			sigaction(SIGSEGV, &sa, NULL);
			sigaction(SIGBUS, &sa, NULL);

			if (round & 1) {
				/* lower the stack limit to force the
				 * kern_plimit vm_map_protect(PROT_NONE) path
				 * concurrently with growth */
				rl.rlim_cur = 4UL << 20;
				rl.rlim_max = RLIM_INFINITY;
				setrlimit(RLIMIT_STACK, &rl);
			}
			/* re-exec ourself as child */
			{
				char self[256], *av[3];
				/* use /proc/curproc/file for self path */
				ssize_t n = readlink("/proc/curproc/file",
						     self, sizeof(self) - 1);
				if (n < 0) {
					perror("readlink");
					_exit(1);
				}
				self[n] = 0;
				av[0] = self;
				av[1] = (char *)"child";
				av[2] = NULL;
				execv(self, av);
				perror("execv");
				_exit(1);
			}
		}
		waitpid(pid, &status, 0);
		if (WIFSIGNALED(status)) {
			printf("round %d: child killed by sig %d\n",
			       round, WTERMSIG(status));
			fflush(stdout);
			if (WTERMSIG(status) != SIGSEGV &&
			    WTERMSIG(status) != SIGBUS) {
				printf("UNEXPECTED SIGNAL (possible panic "
				       "adjacent)\n");
			}
		}
	}

	/* guard harness as a separate fork so its expected SIGSEGV is fine */
	pid = fork();
	if (pid == 0) {
		char self[256], *av[3];
		ssize_t n = readlink("/proc/curproc/file", self, sizeof(self)-1);
		if (n < 0) _exit(1);
		self[n] = 0;
		av[0] = self; av[1] = (char *)"guard"; av[2] = NULL;
		execv(self, av);
		_exit(1);
	}
	waitpid(pid, &status, 0);
	if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) {
		printf("guard harness: SIGSEGV at guard as expected\n");
	} else if (WIFEXITED(status) && WEXITSTATUS(status) >= 2) {
		printf("guard harness: BYPASS code %d\n", WEXITSTATUS(status));
	} else {
		printf("guard harness: status %d/%d\n",
		       WIFEXITED(status) ? WEXITSTATUS(status) : -1,
		       WTERMSIG(status));
	}

	printf("stackstress: all rounds complete, no kernel panic observed\n");
	return 0;
}