DragonFlyBSD Kernel Audit
DF-2816 / sibs.c
← back to finding ↓ download raw
/*
 * DF-2816 - boot()/shutdown_cleanup_proc() vs sibling threads.
 *
 * sys_reboot() -> boot() calls shutdown_cleanup_proc(curproc)
 * (sys/kern/kern_shutdown.c:291) while the calling process's OTHER
 * threads keep running on other CPUs.  shutdown_cleanup_proc()
 *   - kern_closefrom(0) closes every fd,
 *   - cache_drop(&fdp->fd_ncdir) NULLs fd_ncdir.ncp (vfs_cache.c:1090)
 *     while leaving fd_ncdir in place,
 *   - vm_map_remove()s the whole user address space (:601-609).
 *
 * A sibling thread that then performs any path lookup copies the NULL-ed
 * nchandle into nd->nl_nch (vfs_nlookup.c:159-160 cache_copy has no NULL
 * guard) and naccess() dereferences nch->ncp at +0x58
 * (vfs_nlookup.c:658) -> kernel NULL-deref page fault -> panic.
 *
 * Siblings here just retry open("etc/passwd") in a loop (relative path
 * -> fd_ncdir anchor).  No dump-device churn involved at all.
 */
#include <sys/reboot.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

static volatile int stop;

static void *
sib(void *x __unused)
{
	int fd;

	while (!stop) {
		fd = open("etc/passwd", O_RDONLY);	/* relative -> cwd */
		if (fd >= 0)
			close(fd);
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t tid[64];
	int n = 4;
	int i;

	if (argc > 1)
		n = atoi(argv[1]);
	if (n > 64)
		n = 64;
	for (i = 0; i < n; i++)
		pthread_create(&tid[i], NULL, sib, NULL);
	sleep(1);
	printf("sibs: calling reboot(RB_DUMP|RB_NOSYNC) with %d live siblings\n", n);
	fflush(stdout);
	syscall(SYS_reboot, RB_AUTOBOOT | RB_DUMP | RB_NOSYNC);
	printf("sibs: returned?!\n");
	return (0);
}