⬢ DragonFlyBSD Kernel Audit
DF-5515 / ub5515stall_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
/*
 * DF-5515 KLD harness -- production-code demonstration of the ubser
 * (BWCT multi-port USB serial, ucom core) cfg-path synchronous-request
 * stall.  Same rig family as DF-5496 umct / DF-5504 ucycom / DF-5487
 * uvisor (cfg-stall family), strengthened with the DF-5447
 * production-code-include technique:
 *
 *   - the UNMODIFIED production sys/bus/u4b/serial/ubser.c is #included
 *     into this translation unit (newbus registration neutered), so the
 *     REAL static ubser_cfg_set_break() (ubser.c:462-487) runs;
 *   - the REAL kernel usb_process machinery (usb_proc_create /
 *     usb_proc_msignal / usb_proc_mwait / usb_proc_free -- the exact
 *     taskqueue ucom's cfg callbacks execute on, usb_serial.c:322)
 *     schedules and serializes the work;
 *   - only the EP0 sink is modeled: ucom_cfg_do_request is a macro
 *     (usb_serial.h:220-221) expanding to usbd_do_request_proc(), which
 *     this harness defines (there is no prototype in any header, so the
 *     harness pre-declares it).  The model reproduces the production
 *     requester exactly where it matters: it is called with the ucom
 *     taskqueue's lock held (usb_process protocol), sleeps the full
 *     caller-supplied timeout in the "WXFER" wait channel
 *     (usb_request.c:643 style) on a UNIQUE per-request wchan, and for
 *     the "silent evil adapter" returns USB_ERR_TIMEOUT -- which
 *     production ubser DISCARDS (ubser.c:482-485: DPRINTFN only).
 *
 * PRODUCTION REQUEST ACCOUNTING (pass-2 precision -- this driver's
 * entire cfg surface is ONE request):
 *
 *   ubser_callback (ubser.c:175-184) registers ucom_cfg_set_break,
 *   ucom_cfg_get_status, ucom_pre_param, start/stop read/write, poll.
 *   It registers NO ucom_cfg_param, NO ucom_cfg_open/close, NO
 *   set_dtr/rts/ring.  In ucom_cfg_line_state (usb_serial.c:1372-1381)
 *   the callback mask is therefore UCOM_LS_BREAK ONLY:
 *     - first open(2)  (usb_serial.c:842-880): the queued line-state
 *       task computes break-off, set_break(sc, 0) -- ubser.c:470
 *       `if (onoff)' gates it: 0 EP0 requests.  cfg_open has no
 *       callback -> no hz/10 pause (pause is inside the callback-if,
 *       usb_serial.c:773-778).  status task = fake bits (ubser.c:
 *       489-495), 0 requests.  => first open costs ZERO requests.
 *     - every tcsetattr: ubser has no ucom_cfg_param -> the param task
 *       issues 0 requests.
 *     - TIOCSBRK (usb_serial.c:1190-1193 -> ucom_break:1458-1466 ->
 *       ucom_line_state:1423-1445): any_bits&BREAK -> set_break(sc,1)
 *       -> 1 x 1000 ms synchronous EP0 vendor request (ubser.c:480-481).
 *     - TIOCCBRK: set_break(sc,0) -> 0 requests (the `if (onoff)'
 *       gate -- see DF-5517: the clear command is never sent at all).
 *     - a paced TIOCSBRK+TIOCCBRK PAIR coalesces into ONE line-state
 *       task: notch_bits=BREAK -> set_break(prev=0) [0 requests] +
 *       any_bits=BREAK -> set_break(last=1) [1 request] -- the pair
 *       still costs 1 x 1000 ms (usb_serial.c:1384-1420).
 *     - close(2) HUPCL drops DTR only: mask excludes DTR -> 0 requests;
 *       the closer's cost is purely usb_proc_mwait() behind the queue
 *       (usb_serial.c:671-672, uninterruptible cv_wait).
 *     - unplug: ucom_detach -> usb_proc_drain (usb_serial.c:394) waits
 *       out the same queue on the USB hub event thread.
 *
 *   => An evil BWCT adapter that NAKs the VENDOR_SET_BREAK data stage
 *      pins the per-adapter ucom taskqueue 1 s per TIOCSBRK (or per
 *      SBRK/CBRK pair); close(2) of any of the adapter's ttys blocks
 *      uninterruptibly behind it; a sustained break loop extends the
 *      occupancy without bound.  The ttyU and cuaU device nodes are
 *      0600 root / 0660 dialer (usb_serial.c:477-488), so local
 *      amplification needs root or group dialer; the evil device
 *      itself needs nothing.
 *
 * PHASE A  open + 1 x (TIOCSBRK, 100 ms, TIOCCBRK) + close: the set
 *          issues 1 stalled request; the clear task computes
 *          set_break(sc,0) and issues NOTHING (ubser.c:470 gate --
 *          the DF-5517 tie-in).  EXPECT closer ELAPSED ~= 700-900 ms
 *          behind the stalled set request, 1 request total.
 * PHASE B  open + 5 pairs spaced 1.1 s (outpacing the 1 s request so
 *          the double buffer cannot coalesce -- a sustained loop):
 *          every set issues its own request.  EXPECT 5 requests,
 *          ~5.5 s of continuous per-adapter taskqueue occupancy, closer
 *          ELAPSED ~= 900-1100 ms behind the last in-flight request.
 *          The occupancy extends without bound while the abuser keeps
 *          a >=1 s paced break loop running; only UNPACED bursts
 *          coalesce in the double buffer.
 * PHASE B2 slow-answer evil variant: the request SUCCEEDS at
 *          timeout-100 ms and no error is visible anywhere -- same
 *          occupancy, fully invisible to the driver.
 * PHASE C  (fixed build only, fix.diff: timeout 1000 -> 250 ms at
 *          ubser.c:481): same single TIOCSBRK.  EXPECT closer ELAPSED
 *          ~= 200-300 ms.  See run.fixed.log.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/time.h>

/* NOTE: no usb_debug.h here on purpose: ubser.c must include its own
 * debug machinery itself (its USB_DEBUG_VAR-before-usb_debug.h include
 * order is required for DPRINTF/DPRINTFN).  The prototype for
 * usbd_do_request_proc comes from usbdi_util.h (line 42), which
 * ubser.c itself includes at :88 -- visible at the production call
 * site inside ubser_cfg_set_break (ubser.c:480). */

MODULE_DEPEND(ub5515stall, usb, 1, 1, 1);
MODULE_DEPEND(ub5515stall, ucom, 1, 1, 1);

/* Neuter the newbus/module registration the production file would
 * otherwise perform. */
#undef DRIVER_MODULE
#undef DRIVER_MODULE_ORDERED
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, driver, devclass, evh, arg)
#define DRIVER_MODULE_ORDERED(name, busname, driver, devclass, evh, arg, order)
#define MODULE_DEPEND(module, mdepend, vmin, vpref, vmax)
#define MODULE_VERSION(module, version)

/* THE PRODUCTION DRIVER, textually unmodified (md5-gated by build.sh). */
#include "../ubser.c"

/* one adapter instance (the production driver's softc, allocated
 * zeroed by newbus at subr_bus.c:1953-1954 M_ZERO) */
static struct ubser_softc ub_sc;

/* ------------------------------------------------------------------ */
/* Timing helpers / wait idents                                        */

static int ub_tq_dummy;		/* tsleep ident only */
static struct timeval ub_t0;

static int64_t
ub_ms(struct timeval *t0, struct timeval *t1)
{
	return ((int64_t)(t1->tv_sec - t0->tv_sec) * 1000 +
	    (t1->tv_usec - t0->tv_usec) / 1000);
}

static int64_t
ub_abs_ms(void)
{
	struct timeval now;

	microtime(&now);
	return (ub_ms(&ub_t0, &now));
}

/* ------------------------------------------------------------------ */
/* Modeled EP0 sink: the silent / slow evil BWCT adapter              */

static int ub_evil_mode;	/* 0 = silent (timeout), 1 = slow-answer */
static int ub_req_count;	/* requests issued this phase */
static char ub_req_wchan[16];	/* unique per-request wait channel */

usb_error_t
usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc,
    struct usb_device_request *req, void *data, uint16_t flags,
    uint16_t *actlen, usb_timeout_t timeout)
{
	int ms = (int)timeout;

	/* production gate, usb_request.c:771-774 */
	if (usb_proc_is_gone(pproc))
		return (USB_ERR_IOERROR);

	ub_req_count++;
	kprintf("UB5515[%dms]: EP0 bRequest=0x%02x wValue=0x%02x "
	    "(%d ms timeout): device %s\n",
	    (int)ub_abs_ms(), req->bRequest, req->wValue[0], ms,
	    ub_evil_mode ? "answers at just-under-timeout" :
	    "silent -- NAKs until timeout");

	if (ub_evil_mode) {
		/* evil adapter answers successfully at timeout-100 ms */
		lksleep(&ub_req_wchan, pproc->up_lock, 0, "WXFER",
		    USB_MS_TO_TICKS(ms - 100));
		if (req->wLength[0] != 0 && data != NULL)
			*(uint8_t *)data = 0;	/* answer payload */
		return (USB_ERR_NORMAL_COMPLETION);
	}
	/* silent adapter: full timeout, then error -- which production
	 * ubser_cfg_set_break DISCARDS (ubser.c:482-485) */
	lksleep(&ub_req_wchan, pproc->up_lock, 0, "WXFER",
	    USB_MS_TO_TICKS(ms));
	return (USB_ERR_TIMEOUT);
}

/* ------------------------------------------------------------------ */
/* The ucom cfg taskqueue: REAL usb_process machinery                 */

struct ub_msg {
	struct usb_proc_msg hdr;
};
static struct ub_msg ub_open_msg[2];	/* ucom sc_open_task[2] */
static struct ub_msg ub_line_msg[2];	/* ucom sc_line_state_task[2] */
static struct ub_msg ub_close_msg[2];	/* ucom sc_close_task[2] */

/* modeled ucom_softc line-state bits (usb_serial.c:1384-1392
 * arithmetic), BREAK-only mask as computed for ubser at
 * usb_serial.c:1378-1379 */
static int ub_ll_ready;		/* UCOM_FLAG_LL_READY */
static int ub_pls_curr, ub_pls_set, ub_pls_clr;
#define	UB_LS_BREAK	0x04		/* UCOM_LS_BREAK, usb_serial.h:208 */

/*
 * ucom_cfg_open (usb_serial.c:756-780) for a driver with no
 * ucom_cfg_open callback: sets LL_READY; NO pause (the hz/10 pause at
 * :777 is inside the callback-if).
 */
static void
ub_cfg_open_task(struct usb_proc_msg *pm)
{
	kprintf("UB5515[%dms]: cfg-open task: no ucom_cfg_open callback in "
	    "ubser -> set LL_READY only, 0 requests, no pause "
	    "(usb_serial.c:771-778)\n", (int)ub_abs_ms());
	ub_ll_ready = 1;
}

/*
 * ucom_cfg_line_state (usb_serial.c:1356-1421) with the BREAK-only
 * callback mask ubser earns (usb_serial.c:1378-1379), driving the REAL
 * production ubser_cfg_set_break().
 */
static void
ub_cfg_line_task(struct usb_proc_msg *pm)
{
	uint8_t notch_bits, any_bits, prev_value, last_value;

	if (!ub_ll_ready)
		return;		/* usb_serial.c:1368-1370 */

	/* mask arithmetic, usb_serial.c:1384-1392 */
	notch_bits = (uint8_t)((ub_pls_set & ub_pls_clr) & UB_LS_BREAK);
	any_bits = (uint8_t)((ub_pls_set | ub_pls_clr) & UB_LS_BREAK);
	prev_value = (uint8_t)(ub_pls_curr ^ notch_bits);
	last_value = (uint8_t)ub_pls_curr;
	ub_pls_curr = 0;
	ub_pls_set = 0;
	ub_pls_clr = 0;

	kprintf("UB5515[%dms]: line-state task: enter "
	    "(notch=0x%x any=0x%x prev=0x%x last=0x%x)\n",
	    (int)ub_abs_ms(), notch_bits, any_bits, prev_value, last_value);

	/* notch pass, usb_serial.c:1401-1403 */
	if (notch_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (prev_value & UB_LS_BREAK) ? 1 : 0);
	/* last-value pass, usb_serial.c:1415-1417 */
	if (any_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (last_value & UB_LS_BREAK) ? 1 : 0);

	kprintf("UB5515[%dms]: line-state task: leave\n", (int)ub_abs_ms());
}

/* ucom_cfg_close (usb_serial.c:915-921) for a driver with no
 * ucom_cfg_close callback: issues nothing. */
static void
ub_cfg_close_task(struct usb_proc_msg *pm)
{
	kprintf("UB5515[%dms]: cfg-close task: no ucom_cfg_close callback "
	    "in ubser -> 0 requests (usb_serial.c:915-921)\n",
	    (int)ub_abs_ms());
}

/*
 * The user thread: TIOCSBRK / TIOCCBRK as ucomioctl dispatches them
 * (usb_serial.c:1190-1197 -> ucom_break:1458-1466 -> ucom_line_state:
 * 1423-1445: update pls bits, queue the double-buffered line task).
 */
static void
ub_user_break(int on)
{
	if (on) {
		ub_pls_curr |= UB_LS_BREAK;
		ub_pls_set |= UB_LS_BREAK;
	} else {
		ub_pls_curr &= ~UB_LS_BREAK;
		ub_pls_clr |= UB_LS_BREAK;
	}

	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	if (!usb_proc_is_gone(&ub_sc.sc_super_ucom.sc_tq)) {
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_line_msg[0], &ub_line_msg[1]);
		m->hdr.pm_callback = &ub_cfg_line_task;
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
}

/*
 * The closer (or the USB hub event thread on unplug): exactly
 * ucom_close()'s synchronous tail -- queue cfg_close then
 * usb_proc_mwait() UNINTERRUPTIBLY behind whatever is already queued
 * (usb_serial.c:671-672 via ucom_queue_command).
 */
static int64_t
ub_closer(void)
{
	struct timeval t0, t1;

	microtime(&t0);
	kprintf("UB5515[%dms]: closer: close(2) queues cfg_close then "
	    "usb_proc_mwait() uninterruptibly (usb_serial.c:671-672)\n",
	    (int)ub_abs_ms());
	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	if (!usb_proc_is_gone(&ub_sc.sc_super_ucom.sc_tq)) {
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_close_msg[0], &ub_close_msg[1]);
		m->hdr.pm_callback = &ub_cfg_close_task;
		usb_proc_mwait(&ub_sc.sc_super_ucom.sc_tq,
		    &ub_close_msg[0], &ub_close_msg[1]);
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
	microtime(&t1);
	kprintf("UB5515[%dms]: closer: unblocked  ELAPSED=%jd ms\n",
	    (int)ub_abs_ms(), (intmax_t)ub_ms(&t0, &t1));
	return (ub_ms(&t0, &t1));
}

static int
ub_phase(const char *name, char tag, int pairs, int pair_gap_ms,
    int evil_mode)
{
	int i;

	/* fresh adapter instance, exactly as production creates them:
	 * ubser_attach (ubser.c:233) + ucom_attach (usb_serial.c:322) */
	memset(&ub_sc, 0, sizeof(ub_sc));
	lockinit(&ub_sc.sc_lock, "ubser", 0, LK_CANRECURSE);
	ub_sc.sc_numser = 1;		/* evil device claims 1 port */
	ub_sc.sc_udev = (void *)&ub_tq_dummy;	/* model sink ignores it */
	ub_sc.sc_ucom[0].sc_parent = &ub_sc;
	ub_sc.sc_ucom[0].sc_portno = 0;
	ub_sc.sc_ucom[0].sc_super = &ub_sc.sc_super_ucom;
	ub_sc.sc_ucom[0].sc_lock = &ub_sc.sc_lock;
	ub_sc.sc_ucom[0].sc_flag = UCOM_FLAG_LL_READY | UCOM_FLAG_HL_READY;

	ub_ll_ready = 0;
	ub_pls_curr = ub_pls_set = ub_pls_clr = 0;
	ub_req_count = 0;
	ub_evil_mode = evil_mode;

	if (usb_proc_create(&ub_sc.sc_super_ucom.sc_tq, &ub_sc.sc_lock,
	    "ucom", USB_PRI_MED) != 0) {
		kprintf("UB5515: usb_proc_create failed\n");
		lockuninit(&ub_sc.sc_lock);
		return (ENXIO);
	}

	microtime(&ub_t0);
	kprintf("UB5515[%c]: --- phase %s: open + %d SBRK/CBRK pair(s) + "
	    "close, device=%s --- (t=0)\n", tag, name, pairs,
	    evil_mode ? "slow-answer" : "silent-NAK");

	/* open(2): production queue order usb_serial.c:842-880; of the
	 * whole open chain only cfg_open does anything here (param task
	 * bails on !LL_READY, usb_serial.c:1626-1628; line-state computes
	 * break-off -> set_break(0) -> gated off by ubser.c:470). */
	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	{
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_open_msg[0], &ub_open_msg[1]);
		m->hdr.pm_callback = &ub_cfg_open_task;
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
	tsleep(&ub_tq_dummy, 0, "ubopen", hz / 5);	/* open returns */

	/* the user's break traffic.  Within a pair SBRK and CBRK are
	 * 100 ms apart; pairs are spaced pair_gap_ms so that when
	 * pair_gap_ms exceeds the per-request stall every set gets its
	 * own line-state task (a sustained >=1 s paced loop keeps the
	 * taskqueue occupied without end); tighter pacing coalesces in
	 * the double buffer, losing sets -- not gaining them. */
	for (i = 0; i < pairs; i++) {
		ub_user_break(1);		/* TIOCSBRK */
		tsleep(&ub_tq_dummy, 0, "ubsbrk", hz / 10);
		ub_user_break(0);		/* TIOCCBRK */
		if (pair_gap_ms)
			tsleep(&ub_tq_dummy, 0, "ubcbrk",
			    USB_MS_TO_TICKS(pair_gap_ms));
	}

	/* close(2) behind whatever is stalled in the queue */
	ub_closer();

	kprintf("UB5515[%c]: phase done: %d EP0 request(s) issued\n",
	    tag, ub_req_count);

	usb_proc_free(&ub_sc.sc_super_ucom.sc_tq);
	lockuninit(&ub_sc.sc_lock);
	return (0);
}

static void
ub_touch_statics(void)
{

	if (ubser_devclass != NULL)
		kprintf("UB5515: unexpected devclass %p\n", ubser_devclass);
	kprintf("UB5515: production driver \"%s\" compiled into harness "
	    "(registration neutered)\n", ubser_driver.name);
}

static int
ub_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		kprintf("UB5515: DF-5515 ubser cfg-stall harness, hz=%d; "
		    "ubser cfg surface = 1 request (VENDOR_SET_BREAK, "
		    "1000 ms, TIOCSBRK only; TIOCCBRK gated by "
		    "ubser.c:470 -- see DF-5517)\n", hz);
		ub_touch_statics();
		ub_phase("A  open + 1 SBRK/CBRK pair + close (silent evil)",
		    'A', 1, 0, 0);
		ub_phase("B  open + 5 pairs @1.1s + close (silent, sustained)",
		    'B', 5, 1100, 0);
		ub_phase("B2 open + 1 SBRK + close (slow-answer evil)",
		    '2', 1, 0, 1);
		kprintf("UB5515: harness complete (phase C = fixed build "
		    "rerun, see run.fixed.log)\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t ub_mod = { "ub5515stall", ub_modevent, 0 };
DECLARE_MODULE(ub5515stall, ub_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);