⬢ DragonFlyBSD Kernel Audit
DF-5436 / hx5436_openrace.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
/*
 * DF-5436 KLD harness -- ulpt(4) cross-cdev open/close TOCTOU on sc_fflags.
 *
 * Production code under test (sys/bus/u4b/serial/ulpt.c, copied VERBATIM
 * below with only the usbd_xfer and usb_fifo calls stubbed at equivalent
 * cost):
 *
 *   ulpt_open()    ulpt.c:406-420   "we assume that open is a serial process"
 *   unlpt_open()   ulpt.c:422-458   racy EBUSY gate on sc->sc_fflags (:427),
 *                                    unlocked sc_fflags |=  (:456),
 *                                    sc_fifo_open[dir] = fifo (:441/:454)
 *   ulpt_close()   ulpt.c:460-470   unlocked sc_fflags &= ~ (:465)
 *   ulpt_watchdog()ulpt.c:719-735   polls device status iff sc_fflags == 0
 *
 * Framework reality modelled here (sys/bus/u4b/usb_dev.c):
 *   - per-cdev (per-vnode) serialization of open/close  -> per-cdev lock
 *     (usb_open/usb_cdevpriv_dtor run under the devfs vnode lock)
 *   - NO serialization ACROSS the two cdevs of one ulpt softc (ulpt + unlpt
 *     are two independent usb_fifo_attach() pairs, ulpt.c:625-638) -> the
 *     only cross-guard is the racy sc_fflags check.
 *
 * The window between the sc_fflags gate check (ulpt.c:427) and the
 * bookkeeping (ulpt.c:441/454/456) contains real production work:
 * usbd_xfer_set_stall() plus usb_fifo_alloc_buffer() -> usb_alloc_mbufs()
 * kmalloc of 2 x 32KB (ULPT_BSIZE=1<<15, ULPT_IFQ_MAXLEN=2).  The harness
 * performs an equivalent kmalloc(2 * ULPT_BSIZE) in that window -- no
 * artificial sleeps.
 *
 * Counters (sysctl hw.hx5436.*):
 *   opens_while_open   successful open of a direction that is ALREADY open
 *                      (exclusive-open invariant broken; must stay 0)
 *   double_open_simul  ...and another open of that direction is still inside
 *                      its open window (true simultaneous race proof)
 *   polls_while_open   watchdog ticks that would start the 1 Hz status poll
 *                      while a session is actually open (ulpt.c:730 guard
 *                      defeated -- the firmware-bug condition it avoids)
 *   resets_while_open  ulpt_open() SOFT_RESET decisions taken while a
 *                      session is actually open (ulpt.c:413 gate defeated)
 *   a_opens/b_opens    total successful opens per cdev
 *
 * sysctl hw.hx5436.fixed_gate=1 switches gate+bookkeeping to the fix.diff
 * mechanism (sc_lock held across check..commit in open and close); all
 * anomaly counters must then stay 0.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/fcntl.h>

#define	ULPT_BSIZE	(1<<15)		/* ulpt.c:75 */
#define	ULPT_IFQ_MAXLEN	2		/* ulpt.c:76 */

struct ulpt_softc {			/* production softc fields, verbatim */
	int	sc_fflags;		/* current open flags, FREAD/FWRITE */
	uint8_t	sc_iface_no;
	uint8_t	sc_last_status;
	uint8_t	sc_zlps;
};

struct hx_fifo {			/* stand-in for a struct usb_fifo slot */
	int	dir;
	void	*qbuf;			/* usb_fifo_alloc_buffer() mirror */
};

/* persistent softc state, as in production */
static struct ulpt_softc hx_sc;
static struct hx_fifo *hx_sc_fifo_open[2];	/* sc->sc_fifo_open[2] */
static struct hx_fifo	hx_slot[2][2];	/* [cdev 0=a/1=b][dir TX=0,RX=1] */
static struct lock	hx_cdev_lock[2];	/* devfs per-vnode serialization */
static struct lock	hx_sc_lock;		/* sc->sc_lock (fix gate) */

static struct callout	hx_watchdog;

/* ground truth maintained by the framework shim (NOT by the driver copy) */
static int	actually_open[2];	/* per direction */
static int	opening[2];		/* per direction, f_open in flight */

/* anomaly counters */
static int	counter_opens_while_open;
static int	counter_double_simul;
static int	counter_polls_while_open;
static int	counter_resets_while_open;
static int	counter_a_opens, counter_b_opens;
static int	counter_ebusy;

static int	hx_fixed_gate;		/* fix.diff mechanism switch */

/* instrumentation */
static int64_t	window_ns_sum;
static int64_t	window_n;
static int64_t	window_ns_max;
static int	counter_window_crossed;

SYSCTL_NODE(_hw, OID_AUTO, hx5436, CTLFLAG_RW, 0, "DF-5436 harness");
SYSCTL_INT(_hw_hx5436, OID_AUTO, opens_while_open, CTLFLAG_RD,
    &counter_opens_while_open, 0, "opens that broke exclusive-open");
SYSCTL_INT(_hw_hx5436, OID_AUTO, double_open_simul, CTLFLAG_RD,
    &counter_double_simul, 0, "simultaneous same-direction race wins");
SYSCTL_INT(_hw_hx5436, OID_AUTO, polls_while_open, CTLFLAG_RD,
    &counter_polls_while_open, 0, "watchdog polls during open session");
SYSCTL_INT(_hw_hx5436, OID_AUTO, resets_while_open, CTLFLAG_RD,
    &counter_resets_while_open, 0, "SOFT_RESET decisions during open session");
SYSCTL_INT(_hw_hx5436, OID_AUTO, a_opens, CTLFLAG_RD, &counter_a_opens, 0, "");
SYSCTL_INT(_hw_hx5436, OID_AUTO, b_opens, CTLFLAG_RD, &counter_b_opens, 0, "");
SYSCTL_INT(_hw_hx5436, OID_AUTO, ebusy, CTLFLAG_RD, &counter_ebusy, 0, "");
SYSCTL_INT(_hw_hx5436, OID_AUTO, fixed_gate, CTLFLAG_RW,
    &hx_fixed_gate, 0, "1 = fix.diff mechanism (locked gate+commit)");
SYSCTL_QUAD(_hw_hx5436, OID_AUTO, window_ns_sum, CTLFLAG_RD,
    &window_ns_sum, 0, "sum gate->commit latency");
SYSCTL_QUAD(_hw_hx5436, OID_AUTO, window_n, CTLFLAG_RD,
    &window_n, 0, "successful opens");
SYSCTL_QUAD(_hw_hx5436, OID_AUTO, window_ns_max, CTLFLAG_RD,
    &window_ns_max, 0, "max gate->commit latency");
SYSCTL_INT(_hw_hx5436, OID_AUTO, window_crossed, CTLFLAG_RD,
    &counter_window_crossed, 0, "peer committed during our gate window");

/*---------------- production code copies (ulpt.c) ----------------*/

static void
hx_usbd_xfer_set_stall(void *x __unused)
{
	/* production: lockmgr x2 + flag store (ulpt.c:432-434) -- no-op */
}

static int
hx_usb_fifo_alloc_buffer(struct hx_fifo *fifo)
{
	/* production: usb_fifo_alloc_buffer(fifo, usbd_xfer_max_len, 2)
	 * -> usb_alloc_mbufs(M_USBDEV, ..., ULPT_BSIZE, ULPT_IFQ_MAXLEN):
	 * one contiguous ~2x32KB kmalloc (usb_mbuf.c:55-100) */
	if (fifo->qbuf != NULL)
		kfree(fifo->qbuf, M_TEMP);
	fifo->qbuf = kmalloc(ULPT_BSIZE * ULPT_IFQ_MAXLEN, M_TEMP,
	    M_WAITOK | M_ZERO);
	return (fifo->qbuf != NULL ? 0 : ENOMEM);
}

static void
hx_usb_fifo_free_buffer(struct hx_fifo *fifo)
{
	/* production: kfree(queue_data) + queue reset (usb_dev.c:2009-2020) */
	if (fifo->qbuf != NULL) {
		kfree(fifo->qbuf, M_TEMP);
		fifo->qbuf = NULL;
	}
}

/*
 * ulpt.c:422-458 -- VERBATIM logic, stubs for the usb calls.
 * (instrumentation: window_ns_* measure gate-pass -> commit latency,
 * window_crossed counts canary hits where a peer committed during our
 * own gate window -- both are harness-only observations)
 */
static int
unlpt_open_copy(int cdev, int fflags)
{
	struct hx_fifo *fifo;
	int64_t ns0, ns1;
	struct timespec ts;
	int canary;

	if (hx_sc.sc_fflags & fflags) {
		return (EBUSY);			/* ulpt.c:427-429 (racy gate) */
	}
	getnanouptime(&ts);
	ns0 = (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
	canary = hx_sc.sc_fflags;		/* re-read after gate */
	if (fflags & FREAD) {
		/* clear stall first */
		hx_usbd_xfer_set_stall(NULL);
		fifo = &hx_slot[cdev][1];		/* USB_FIFO_RX */
		if (hx_usb_fifo_alloc_buffer(fifo)) {
			return (ENOMEM);
		}
		/* set which FIFO is opened */
		hx_sc_fifo_open[1] = fifo;		/* ulpt.c:441 */
	}
	if (fflags & FWRITE) {
		hx_usbd_xfer_set_stall(NULL);
		fifo = &hx_slot[cdev][0];		/* USB_FIFO_TX */
		if (hx_usb_fifo_alloc_buffer(fifo)) {
			return (ENOMEM);
		}
		hx_sc_fifo_open[0] = fifo;		/* ulpt.c:454 */
	}
	hx_sc.sc_fflags |= fflags & (FREAD | FWRITE);	/* ulpt.c:456 */
	getnanouptime(&ts);
	ns1 = (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
	window_ns_sum += ns1 - ns0;
	window_n++;
	if (ns1 - ns0 > window_ns_max)
		window_ns_max = ns1 - ns0;
	if (canary & fflags)
		counter_window_crossed++;
	return (0);
}

/* ulpt.c:406-420 */
static int
ulpt_open_copy(int cdev, int fflags)
{
	/* we assume that open is a serial process */	/* ulpt.c:411 */
	if (hx_sc.sc_fflags == 0) {
		if (actually_open[0] + actually_open[1] +
		    opening[0] + opening[1] > 0)
			counter_resets_while_open++;
		/* ulpt_reset(sc): 2 synchronous EP0 requests, ulpt.c:177-187 */
	}
	return (unlpt_open_copy(cdev, fflags));
}

/* ulpt.c:460-470 -- VERBATIM logic: production frees the CLOSING fifo's
 * buffer (usb_fifo_free_buffer(fifo)); sc_fifo_open[] is left dangling */
static void
ulpt_close_copy(int cdev, int fflags)
{
	hx_sc.sc_fflags &= ~(fflags & (FREAD | FWRITE));	/* ulpt.c:465 */

	if (fflags & FREAD)
		hx_usb_fifo_free_buffer(&hx_slot[cdev][1]);
	if (fflags & FWRITE)
		hx_usb_fifo_free_buffer(&hx_slot[cdev][0]);
	/* NOTE: production never does sc_fifo_open[dir] = NULL */
}

/* ulpt.c:719-735 -- watchdog condition verbatim */
static void
ulpt_watchdog_copy(void *arg __unused)
{
	int open_now = actually_open[0] + actually_open[1];

	if (hx_sc.sc_fflags == 0) {
		/* usbd_transfer_start(status poll) would happen here */
		if (open_now > 0)
			counter_polls_while_open++;
	}
	callout_reset(&hx_watchdog, hz, ulpt_watchdog_copy, NULL);
}

/*---------------- fixed-gate variant (fix.diff mechanism) ----------------*/

static int
unlpt_open_fixed(int cdev, int fflags)
{
	int rv;

	lockmgr(&hx_sc_lock, LK_EXCLUSIVE);
	rv = unlpt_open_copy(cdev, fflags);
	lockmgr(&hx_sc_lock, LK_RELEASE);
	return (rv);
}

static void
ulpt_close_fixed(int cdev, int fflags)
{
	lockmgr(&hx_sc_lock, LK_EXCLUSIVE);
	ulpt_close_copy(cdev, fflags);
	lockmgr(&hx_sc_lock, LK_RELEASE);
}

/*---------------- framework shim: the two cdevs ----------------*/

static d_open_t	hx_a_open, hx_b_open;
static d_close_t hx_a_close, hx_b_close;

static struct dev_ops hx_a_ops = {
	{ "hx5436a", 0, D_MPSAFE },		/* production: usb_ops (D_MPSAFE) */
	.d_open = hx_a_open,
	.d_close = hx_a_close,
};
static struct dev_ops hx_b_ops = {
	{ "hx5436b", 0, D_MPSAFE },
	.d_open = hx_b_open,
	.d_close = hx_b_close,
};

static cdev_t hx_a_dev, hx_b_dev;

static int
hx_open_common(int cdev, struct dev_open_args *ap, struct lock *cdev_lock)
{
	int fflags = ap->a_oflags & (FREAD | FWRITE);
	int is_rx = (fflags & FREAD) != 0;
	int is_tx = (fflags & FWRITE) != 0;
	int rv;

	/* per-cdev serialization only -- the production cross-cdev race is
	 * exactly the absence of anything wider (devfs per-vnode lock) */
	lockmgr(cdev_lock, LK_EXCLUSIVE);

	if (is_rx)
		atomic_add_int(&opening[1], 1);
	if (is_tx)
		atomic_add_int(&opening[0], 1);

	if (cdev == 0)				/* /dev/ulpt0 role */
		rv = (hx_fixed_gate ?
		    unlpt_open_fixed(0, fflags) : ulpt_open_copy(0, fflags));
	else					/* /dev/unlpt0 role */
		rv = (hx_fixed_gate ?
		    unlpt_open_fixed(1, fflags) : unlpt_open_copy(1, fflags));

	if (rv == 0) {
		/* exclusive-open invariant check (ground truth) */
		if ((is_rx && actually_open[1] > 0) ||
		    (is_tx && actually_open[0] > 0))
			atomic_add_int(&counter_opens_while_open, 1);
		if ((is_rx && opening[1] > 1) || (is_tx && opening[0] > 1))
			atomic_add_int(&counter_double_simul, 1);
		if (cdev == 0)
			atomic_add_int(&counter_a_opens, 1);
		else
			atomic_add_int(&counter_b_opens, 1);
		/* framework shim: open completed */
		if (is_rx)
			atomic_add_int(&actually_open[1], 1);
		if (is_tx)
			atomic_add_int(&actually_open[0], 1);
	} else if (rv == EBUSY) {
		atomic_add_int(&counter_ebusy, 1);
	}

	if (is_rx)
		atomic_add_int(&opening[1], -1);
	if (is_tx)
		atomic_add_int(&opening[0], -1);

	lockmgr(cdev_lock, LK_RELEASE);
	return (rv);
}

static int
hx_close_common(int cdev, struct dev_close_args *ap, struct lock *cdev_lock)
{
	int fflags = ap->a_fflag & (FREAD | FWRITE);

	lockmgr(cdev_lock, LK_EXCLUSIVE);

	/* framework: stop + unref happen BEFORE f_close (usb_dev.c:842-857) */
	if (fflags & FREAD)
		atomic_add_int(&actually_open[1], -1);
	if (fflags & FWRITE)
		atomic_add_int(&actually_open[0], -1);

	if (hx_fixed_gate)
		ulpt_close_fixed(cdev, fflags);
	else
		ulpt_close_copy(cdev, fflags);

	lockmgr(cdev_lock, LK_RELEASE);
	return (0);
}

static int
hx_a_open(struct dev_open_args *ap) { return (hx_open_common(0, ap, &hx_cdev_lock[0])); }
static int
hx_b_open(struct dev_open_args *ap) { return (hx_open_common(1, ap, &hx_cdev_lock[1])); }
static int
hx_a_close(struct dev_close_args *ap) { return (hx_close_common(0, ap, &hx_cdev_lock[0])); }
static int
hx_b_close(struct dev_close_args *ap) { return (hx_close_common(1, ap, &hx_cdev_lock[1])); }

/*---------------- module boilerplate ----------------*/

static int
hx5436_modevent(module_t mod, int type, void *arg __unused)
{
	switch (type) {
	case MOD_LOAD:
		memset(&hx_sc, 0, sizeof(hx_sc));
		memset(hx_slot, 0, sizeof(hx_slot));
		memset(hx_sc_fifo_open, 0, sizeof(hx_sc_fifo_open));
		memset(actually_open, 0, sizeof(actually_open));
		memset(opening, 0, sizeof(opening));
		counter_opens_while_open = 0;
		counter_double_simul = 0;
		counter_polls_while_open = 0;
		counter_resets_while_open = 0;
		counter_a_opens = counter_b_opens = counter_ebusy = 0;
		counter_window_crossed = 0;
		window_ns_sum = window_n = window_ns_max = 0;
		hx_fixed_gate = 0;
		lockinit(&hx_sc_lock, "hx5436lk", 0, LK_CANRECURSE);
		lockinit(&hx_cdev_lock[0], "hx5436a", 0, 0);
		lockinit(&hx_cdev_lock[1], "hx5436b", 0, 0);
		callout_init_lk(&hx_watchdog, &hx_sc_lock);
		callout_reset(&hx_watchdog, hz, ulpt_watchdog_copy, NULL);
		hx_a_dev = make_dev(&hx_a_ops, 0, UID_ROOT, GID_WHEEL,
		    0666, "hx5436a");
		hx_b_dev = make_dev(&hx_b_ops, 0, UID_ROOT, GID_WHEEL,
		    0666, "hx5436b");
		kprintf("HX5436: loaded (production racy gate; "
		    "sysctl hw.hx5436.fixed_gate=1 for fix mechanism)\n");
		return (0);
	case MOD_UNLOAD:
		if (actually_open[0] || actually_open[1]) {
			/* counter drift from the racy gate is itself
			 * evidence; the racer is joined before unload */
			kprintf("HX5436: ground-truth drift (actually_open="
			    "%d,%d) -- the race desyncs shared state\n",
			    actually_open[0], actually_open[1]);
		}
		callout_stop(&hx_watchdog);
		destroy_dev(hx_a_dev);
		destroy_dev(hx_b_dev);
		lockuninit(&hx_cdev_lock[0]);
		lockuninit(&hx_cdev_lock[1]);
		lockuninit(&hx_sc_lock);
		kprintf("HX5436: unloaded: opens_while_open=%d "
		    "double_simul=%d polls_while_open=%d resets_while_open=%d "
		    "a=%d b=%d ebusy=%d\n",
		    counter_opens_while_open, counter_double_simul,
		    counter_polls_while_open, counter_resets_while_open,
		    counter_a_opens, counter_b_opens, counter_ebusy);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t hx5436_mod = {
	"hx5436openrace",
	hx5436_modevent,
	NULL
};
DECLARE_MODULE(hx5436openrace, hx5436_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);