⬢ DragonFlyBSD Kernel Audit
DF-5499 / passsim.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
/*
 * passsim.c -- fake CAM SIM for scsi_pass.c pass-2 PoC (DF-5499 evidence rig,
 * built on the DF-5240 evilsim / DF-5375 targsim pattern).
 *
 * Provides:
 *   - registered SIM ("passsim") with a direct-access device at (bus,0,0),
 *     normal INQUIRY/TUR so probe announces it and da+pass periphs attach
 *     (a new /dev/passN appears).
 *   - sysctl hw.passsim.hang=1   : hold every XPT_SCSI_IO except INQUIRY
 *                                  (CAMIOCOMMAND threads block in
 *                                  cam_periph_runccb/ccbwait forever).
 *   - sysctl hw.passsim.lost=1   : xpt_async(AC_LOST_DEVICE) on (bus,0,0)
 *                                  -> pass periph invalidate; refcount==0
 *                                  -> camperiphfree() frees periph+softc+path
 *                                  under the still-blocked ioctl thread.
 *   - sysctl hw.passsim.poison=1 : kmalloc(M_DEVBUF) objects of
 *                                  sizeof(cam_periph)/sizeof(pass_softc
 *                                  clone)/sizeof(cam_path) filled 0x41 to
 *                                  reclaim+poison the freed memory.
 *   - sysctl hw.passsim.complete=1 : xpt_done() all held CCBs (CAM_REQ_CMP)
 *                                  -> completion walks the freed ccb->path
 *                                  and wakes the ioctl thread into the
 *                                  freed-softc devstat path.
 *   - sysctl hw.passsim.path_id : bus path id (informational).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/sysctl.h>
#include <sys/callout.h>
#include <sys/devicestat.h>

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

/* verbatim clone of scsi_pass.c:58-83 so poison allocations match the
 * kfree'd object sizes exactly */
typedef enum { PASS_SIM_FLAG_OPEN = 0x01 } pass_sim_flags;
typedef enum { PASS_SIM_STATE_NORMAL } pass_sim_state;
struct pass_sim_softc {
	pass_sim_state	state;
	pass_sim_flags	flags;
	u_int8_t	pd_type;
	union ccb	saved_ccb;
	struct devstat	device_stats;
};

static struct lock	ev_lock;
static struct sysctl_ctx_list ev_sysctl_ctx;
static struct cam_sim	*ev_sim;
static int		ev_path_id = -1;
static int		hang;		/* hold SCSI_IO (except INQ) */
static int		held_total;
static union ccb	*held[256];
static int		nheld;

static void ev_action(struct cam_sim *sim, union ccb *ccb);
static void ev_poll(struct cam_sim *sim);
static void ev_scan_done(struct cam_periph *periph, union ccb *done_ccb);
static int ev_sysctl_lost(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_poison(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_complete(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_stat(SYSCTL_HANDLER_ARGS);

static u_int8_t ev_inq36[36] = {
	0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
	'E','V','I','L','S','I','M',' ',
	'P','A','S','S','U','A','F',' ',
	'1','.','0',' ',
};

static void
ev_complete_ok(union ccb *ccb)
{
	ccb->ccb_h.status = CAM_REQ_CMP;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
ev_hold(union ccb *ccb)
{
	held_total++;
	if (nheld < (int)NELEM(held)) {
		held[nheld++] = ccb;
		kprintf("[PASSSIM] holding CCB %p (func=%#x)\n",
		    ccb, ccb->ccb_h.func_code);
	} else {
		ev_complete_ok(ccb);
	}
}

static void
ev_fill_pathinq(struct ccb_pathinq *cpi, struct cam_sim *sim)
{
	cpi->version_num = 1;
	cpi->hba_inquiry = 0;
	cpi->target_sprt = 0;
	cpi->hba_misc = 0;
	cpi->hba_eng_cnt = 0;
	cpi->max_target = 0;
	cpi->max_lun = 0;
	cpi->initiator_id = 7;
	cpi->unit_number = cam_sim_unit(sim);
	cpi->bus_id = cam_sim_bus(sim);
	cpi->base_transfer_speed = 3300;
	cpi->transport = XPORT_SPI;
	cpi->transport_version = 2;
	cpi->protocol = PROTO_SCSI;
	cpi->protocol_version = SCSI_REV_2;
	cpi->maxio = 65536;
	strncpy(cpi->sim_vid, "EVIL", SIM_IDLEN);
	strncpy(cpi->hba_vid, "EVIL", HBA_IDLEN);
	strncpy(cpi->dev_name, "passsim", DEV_IDLEN);
	cpi->ccb_h.status = CAM_REQ_CMP;
}

static void
ev_action(struct cam_sim *sim, union ccb *ccb)
{
	struct ccb_hdr *ccbh = &ccb->ccb_h;

	switch (ccbh->func_code) {
	case XPT_PATH_INQ:
		ev_fill_pathinq(&ccb->cpi, sim);
		break;			/* immediate */

	case XPT_SCSI_IO:
	{
		struct ccb_scsiio *csio = &ccb->csio;
		u_int8_t *cdb;
		int copy, dxfer = csio->dxfer_len;

		if (ccbh->flags & CAM_CDB_POINTER)
			cdb = csio->cdb_io.cdb_ptr;
		else
			cdb = csio->cdb_io.cdb_bytes;

		if (hang && cdb[0] != 0x12) {	/* hold all but INQUIRY */
			ev_hold(ccb);
			return;
		}
		switch (cdb[0]) {
		case 0x12:		/* INQUIRY */
			copy = dxfer < 36 ? dxfer : 36;
			bcopy(ev_inq36, csio->data_ptr, copy);
			csio->resid = dxfer - copy;
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		case 0x00:		/* TEST UNIT READY */
		case 0x03:		/* REQUEST SENSE */
		case 0x08:		/* READ(6) */
		case 0x25:		/* READ CAPACITY */
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		default:
			ccb->ccb_h.status = CAM_REQ_INVALID;
			if (ccbh->func_code & XPT_FC_QUEUED)
				xpt_done(ccb);
			return;
		}
	}

	default:
		ccbh->status = CAM_REQ_INVALID;
		if (ccbh->func_code & XPT_FC_QUEUED)
			xpt_done(ccb);
		break;
	}
}

static void
ev_poll(struct cam_sim *sim)
{
}

static void
ev_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	xpt_free_path(done_ccb->ccb_h.path);
	xpt_free_ccb(&done_ccb->ccb_h);
}

static int
ev_sysctl_stat(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	struct cam_path *path;
	struct cam_periph *periph;
	cam_status status;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 0)
		return (0);

	/* deliberately NOT taking ev_lock: this must work even while
	 * other CAM paths are wedged, to report the pass periph state */
	status = xpt_create_path(&path, NULL, ev_path_id, 0, 0);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] stat: create_path failed %#x\n", status);
		return (0);
	}
	periph = cam_periph_find(path, "pass");
	if (periph == NULL) {
		kprintf("[PASSSIM] stat: pass periph NOT FOUND "
		    "(freed or never attached)\n");
	} else {
		kprintf("[PASSSIM] stat: pass periph %p refcount=%u "
		    "flags=%#x %s\n", periph, periph->refcount,
		    periph->flags,
		    (periph->flags & 0x08) ? "INVALID" : "valid");
	}
	xpt_free_path(path);
	return (0);
}

static int
ev_sysctl_lost(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	struct cam_path *path;
	cam_status status;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 0)
		return (0);

	kprintf("[PASSSIM] lost: acquiring sim lock\n");
	cam_sim_lock(&ev_lock);
	kprintf("[PASSSIM] lost: got sim lock; create path\n");
	status = xpt_create_path(&path, NULL, ev_path_id, 0, 0);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] lost: create_path failed %#x\n", status);
		cam_sim_unlock(&ev_lock);
		return (0);
	}
	kprintf("[PASSSIM] lost: broadcasting AC_LOST_DEVICE on bus %d\n",
	    ev_path_id);
	xpt_async(AC_LOST_DEVICE, path, NULL);
	xpt_free_path(path);
	kprintf("[PASSSIM] lost: AC_LOST_DEVICE done\n");
	cam_sim_unlock(&ev_lock);
	return (0);
}

static int
ev_sysctl_poison(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	int i;
	void *z[6];
	size_t sz[3];
	int nalloc = 0;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 0)
		return (0);

	sz[0] = sizeof(struct cam_periph);
	sz[1] = sizeof(struct pass_sim_softc);
	sz[2] = 32;	/* sizeof(struct cam_path) (opaque outside cam_xpt.c) */
	kprintf("[PASSSIM] poison: spraying sizes %zu/%zu/%zu\n",
	    sz[0], sz[1], sz[2]);
	for (i = 0; i < (int)NELEM(z); i++) {
		z[i] = kmalloc(sz[i % 3], M_DEVBUF, M_INTWAIT);
		if (z[i]) {
			memset(z[i], 0x41, sz[i % 3]);
			nalloc++;
		}
	}
	kprintf("[PASSSIM] poison: %d objects allocated+painted 0x41\n",
	    nalloc);
	return (0);
}

static int
ev_sysctl_complete(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 0)
		return (0);

	cam_sim_lock(&ev_lock);
	kprintf("[PASSSIM] completing %d held CCBs\n", nheld);
	while (nheld > 0) {
		union ccb *ccb = held[--nheld];
		ccb->ccb_h.status = CAM_REQ_CMP;
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		if (ccb->ccb_h.func_code & XPT_FC_QUEUED)
			xpt_done(ccb);
	}
	cam_sim_unlock(&ev_lock);
	return (0);
}

static void
ev_choreography(void *arg)
{
	struct cam_path *path;
	union ccb *ccb;
	cam_status status;

	ev_sim = cam_sim_alloc(ev_action, ev_poll, "passsim", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[PASSSIM] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[PASSSIM] xpt_bus_register failed\n");
		cam_sim_unlock(&ev_lock);
		kthread_exit();
	}
	ev_path_id = cam_sim_path(ev_sim);

	status = xpt_create_path(&path, xpt_periph, ev_path_id,
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&ev_lock);
		kthread_exit();
	}
	ccb = xpt_alloc_ccb();
	xpt_setup_ccb(&ccb->ccb_h, path, 5);
	ccb->ccb_h.func_code = XPT_SCAN_BUS;
	ccb->ccb_h.cbfcnp = ev_scan_done;
	xpt_action(ccb);

	kprintf("[PASSSIM] bus %d registered+scanned; pass/da periphs soon\n",
	    ev_path_id);
	cam_sim_unlock(&ev_lock);
	kthread_exit();
}

static int
passsim_modevent(module_t mod, int type, void *data)
{
	int error = 0;
	struct sysctl_ctx_list *ctx;
	struct sysctl_oid *tree;

	switch (type) {
	case MOD_LOAD:
		lockinit(&ev_lock, "passsim", 0, LK_CANRECURSE);
		hang = 0;
		nheld = 0;
		sysctl_ctx_init(&ev_sysctl_ctx);
		ctx = &ev_sysctl_ctx;
		tree = SYSCTL_ADD_NODE(ctx, SYSCTL_STATIC_CHILDREN(_hw),
		    OID_AUTO, "passsim", CTLFLAG_RD, NULL, "passsim rig");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "path_id", CTLFLAG_RD, &ev_path_id, 0, "bus path id");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "hang", CTLFLAG_RW, &hang, 0, "hold SCSI_IO");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "held_total", CTLFLAG_RD, &held_total, 0,
		    "total CCBs ever held");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "stat", CTLFLAG_RW, 0, 0, ev_sysctl_stat, "I",
		    "report pass periph refcount/flags");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "lost", CTLFLAG_RW, 0, 0, ev_sysctl_lost, "I",
		    "broadcast AC_LOST_DEVICE");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "poison", CTLFLAG_RW, 0, 0, ev_sysctl_poison, "I",
		    "spray/paint freed-size objects 0x41");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "complete", CTLFLAG_RW, 0, 0, ev_sysctl_complete, "I",
		    "complete all held CCBs");
		kthread_create(ev_choreography, NULL, NULL, "passsim");
		break;
	case MOD_UNLOAD:
		kprintf("[PASSSIM] unload not supported\n");
		error = EBUSY;
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}


static moduledata_t passsim_mod = { "passsim", passsim_modevent, NULL };
DECLARE_MODULE(passsim, passsim_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(passsim, cam, 1, 1, 1);