⬢ DragonFlyBSD Kernel Audit
DF-2310 / iicbb_harness3.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
/*
 * iicbb_harness.c -- DF-5539 harness part 2: instantiate the real iicbb
 * driver under a fake GPIO parent and drive its whole state machine.
 *
 * Target: sys/bus/iicbus/iicbb.c:164-186 iicbb_print_child() declares
 *   u_char oldaddr;			(iicbb.c:169 -- NEVER initialized)
 *   error = IICBB_RESET(parent, IIC_FASTEST, 0, &oldaddr);
 *   if (error == IIC_ENOADDR) { master-only }
 *   else { IICBB_RESET(parent, IIC_FASTEST, oldaddr, NULL);  <-- restore
 *          kprintf(" on %s addr 0x%x\n", ..., oldaddr & 0xff); } <== LEAK
 *
 * Every in-tree iicbb parent returns IIC_ENOADDR from its reset without
 * writing *oldaddr -- EXCEPT lpbb_reset()'s ppbus-busy error path
 * (sys/dev/misc/lpbb/lpbb.c:186-190), which returns 0 (== IIC_NOERR)
 * without writing *oldaddr either.  So whenever the else-branch runs at
 * all, `oldaddr` is an uninitialized kernel stack byte that is printed
 * to the console and written back as the "restored" bus address.
 *
 * fakebb_reset() below faithfully reproduces the lpbb ppbus-busy
 * contract violation: return 0, never write *oldaddr.
 *
 * The harness also:
 *   - pre-sprays the kernel stack with a compile-time pattern
 *     (spray_conf.h) right before newbus triggers the attach cascade, so
 *     a leaked byte equal to the pattern proves the disclosure end-to-end;
 *   - drives the real transfer state machine through the public iicbus
 *     API against an emulated I2C slave (write-echo, read-pattern, NACK
 *     -> IIC_ENOACK error path + bus-stopped, repeated-start+read).
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/types.h>

#include <bus/iicbus/iiconf.h>
#include <bus/iicbus/iicbus.h>

#include "iicbb_if.h"
#include "spray_conf.h"		/* SPRAY_BYTE / SPRAY_ENABLE, per variant */

#define FAKEBB_NACK_ADDR	0x6e	/* slave refuses this address */
#define FAKEBB_TX_BASE		0xc0	/* slave TX pattern base */

struct fakebb_softc {
	device_t	iicbb;		/* our iicbb child */

	/* emulated GPIO pin state (what the master drove) */
	int		sda, scl, last_scl;

	/* emulated I2C slave */
	int		started;	/* START seen and no STOP yet */
	int		clk;		/* SCL rising edges this byte slot (1..9) */
	u_char		shift;		/* shift-in register */
	int		first_byte;	/* next byte slot is the address */
	int		reading;	/* current transfer direction */
	u_char		addr;		/* addressed slave byte */
	int		nack_this;	/* NACK the current address */
	u_char		rx[32];		/* bytes captured from master */
	int		rxlen;
	u_char		txnext;		/* next TX pattern byte */
	u_char		txcur;		/* current TX byte being clocked out */

	int		starts, stops;
	int		resets, resets_oldaddr;
};

static int spray_sink;
static struct fakebb_softc *fakebb_sc0;
static int fakebb3_dump_arm;
static int fakebb3_clear_arm;			/* keeps the spray from being elided */

static void
fakebb_spray_one(int depth)
{
	volatile char pad[160];
	int i;

	for (i = 0; i < (int)sizeof(pad); i++)
		pad[i] = (char)SPRAY_BYTE;
	if (depth > 0)
		fakebb_spray_one(depth - 1);
	spray_sink += pad[depth % (int)sizeof(pad)];
}

static void
fakebb_spray(void)
{
	if (SPRAY_ENABLE) {
		/* ~5 KB below this frame: deep enough to blanket the
		 * probe/attach cascade (print_child is ~2 KB down) while
		 * staying clear of the kstack guard page (64 frames here
		 * double-faulted on the guest -- keep it modest). */
		fakebb_spray_one(24);
		kprintf("[fakebb] stack pre-loaded with 0x%02x pattern\n",
		    SPRAY_BYTE & 0xff);
	}
}

/* ------------------------------------------------------------------ */
/* emulated wire: what a master reading SDA would see                  */
/* ------------------------------------------------------------------ */

static int
fakebb_wire_sda(struct fakebb_softc *sc)
{

	if (!sc->started)
		return (sc->sda);
	if (sc->clk == 9) {
		/* ACK slot: the slave owns the wire */
		return (sc->nack_this ? 1 : 0);
	}
	if (sc->reading && !sc->first_byte && sc->clk >= 1 && sc->clk <= 8)
		return ((sc->txcur >> (8 - sc->clk)) & 1);
	return (sc->sda);		/* slave idle: master's own level */
}

/* ------------------------------------------------------------------ */
/* iicbb_if methods (the "fake GPIO" vtable)                           */
/* ------------------------------------------------------------------ */

static void
fakebb_setsda(device_t dev, int val)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	val = val ? 1 : 0;
	if (sc->scl && sc->sda && !val) {
		/* SDA falling while SCL high: START / repeated START */
		sc->started = 1;
		sc->clk = 0;
		sc->shift = 0;
		sc->first_byte = 1;
		sc->reading = 0;
		sc->nack_this = 0;
		sc->rxlen = 0;
		sc->starts++;
	} else if (sc->scl && !sc->sda && val && sc->started) {
		/* SDA rising while SCL high: STOP */
		sc->started = 0;
		sc->stops++;
	}
	sc->sda = val;
}

static void
fakebb_setscl(device_t dev, int val)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	val = val ? 1 : 0;
	if (sc->started) {
		if (val && !sc->last_scl) {
			/* rising edge: data bits are sampled here */
			sc->clk++;
			if (sc->clk >= 1 && sc->clk <= 8)
				sc->shift = (u_char)((sc->shift << 1) |
				    (fakebb_wire_sda(sc) & 1));
		} else if (!val && sc->last_scl && sc->clk == 9) {
			/* falling edge of the ACK clock: byte slot done */
			if (sc->first_byte) {
				sc->addr = sc->shift;
				sc->first_byte = 0;
				sc->reading = sc->shift & 1;
				sc->nack_this =
				    (sc->addr == FAKEBB_NACK_ADDR);
				sc->txnext = FAKEBB_TX_BASE;
				if (sc->reading)
					sc->txcur = sc->txnext++;
			} else if (sc->reading) {
				sc->txcur = sc->txnext++;
			} else {
				if (sc->rxlen < (int)sizeof(sc->rx))
					sc->rx[sc->rxlen] = sc->shift;
				sc->rxlen++;
			}
			sc->clk = 0;
			sc->shift = 0;
		}
	}
	sc->last_scl = val;
	sc->scl = val;
}

static int
fakebb_getsda(device_t dev)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	return (fakebb_wire_sda(sc));
}

static int
fakebb_getscl(device_t dev)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	return (sc->scl);
}

/*
 * Faithful reproduction of lpbb_reset()'s ppbus-busy error path
 * (sys/dev/misc/lpbb/lpbb.c:186-190): "success" (0 == IIC_NOERR) is
 * returned but *oldaddr is NEVER written.  iicbb_print_child() takes
 * exactly this into its else-branch and prints/restores the
 * uninitialized byte.
 */
static int
fakebb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	sc->resets++;
	if (oldaddr != NULL)
		sc->resets_oldaddr++;	/* counted, NEVER written */

	(void)speed;
	(void)addr;
	return (0);
}

static int
fakebb_callback(device_t dev, int index, caddr_t data)
{

	(void)dev;
	(void)index;
	(void)data;
	return (0);
}

/* ------------------------------------------------------------------ */
/* fakebb bus device plumbing                                          */
/* ------------------------------------------------------------------ */

static void
fakebb_identify(driver_t *driver, device_t parent)
{
	device_t child;

	child = device_find_child(parent, "fakebb", 0);
	if (child == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "fakebb", 0);

	/*
	 * Spray AFTER adding the child so the child-creation kmalloc noise
	 * precedes the pattern; the probe/attach cascade (which reaches
	 * iicbb_print_child) then runs over the sprayed stack --
	 * sys/kern/subr_bus.c:3007-3013 calls identify before probing the
	 * children it created.
	 */
	fakebb_spray();
}

static int
fakebb_probe(device_t dev)
{

	device_set_desc(dev, "fake GPIO bit-bang i2c master+slave");
	return (0);
}

static int
fakebb_attach(device_t dev)
{
	struct fakebb_softc *sc = device_get_softc(dev);
	device_t bus = NULL;
	devclass_t dc;
	char wbuf[8], rbuf[8], byte;
	int i, error, sent, rlen;
	int ok_w = 1, ok_r = 1;
	int tries;

	sc->iicbb = device_add_child(dev, "iicbb", -1);
	if (sc->iicbb == NULL)
		return (ENXIO);

	/*
	 * The (fakebb,iicbb) driver pair was registered by iicbb_reg.ko
	 * before us, so this cascades synchronously: iicbb0 attaches, adds
	 * its iicbus child, iicbus0 probes+attaches and iicbb_print_child
	 * fires (the LEAK) during device_probe_and_attach -- see
	 * sys/kern/subr_bus.c:2004.
	 */
	bus_generic_attach(dev);

	/* locate the iicbus grandchild (belt and braces) */
	for (tries = 0; tries < 50 && bus == NULL; tries++) {
		dc = devclass_find("iicbus");
		if (dc != NULL)
			bus = devclass_get_device(dc, 0);
		if (bus == NULL)
			tsleep(sc, 0, "fkbw", hz / 20);
	}

	kprintf("[fakebb] attach done: resets=%d resets_with_oldaddr=%d "
	    "bus=%p\n", sc->resets, sc->resets_oldaddr, bus);

	if (bus == NULL) {
		kprintf("[fakebb] ERROR: no iicbus grandchild found\n");
		return (0);		/* leak evidence stands regardless */
	}

	/* ---------------- drive the real state machine ---------------- */
	error = iicbus_request_bus(bus, dev, IIC_WAIT | IIC_INTR);
	kprintf("[fakebb] request_bus error=%d\n", error);

	/* 1. block write of a pattern; expect the slave to capture it */
	wbuf[0] = 0xde; wbuf[1] = 0xad; wbuf[2] = 0xbe; wbuf[3] = 0xef;
	wbuf[4] = 0x55; wbuf[5] = 0xaa; wbuf[6] = 0x12; wbuf[7] = 0x34;
	error = iicbus_block_write(bus, 0x50, wbuf, 8, &sent);
	if (error != 0 || sent != 8)
		ok_w = 0;
	for (i = 0; i < 8; i++)
		if (sc->rx[i] != (u_char)wbuf[i])
			ok_w = 0;
	kprintf("[fakebb] write: error=%d sent=%d "
	    "slave_rx=%02x%02x%02x%02x%02x%02x%02x%02x ok=%d\n",
	    error, sent, sc->rx[0], sc->rx[1], sc->rx[2], sc->rx[3],
	    sc->rx[4], sc->rx[5], sc->rx[6], sc->rx[7], ok_w);

	/* 2. block read; expect the slave TX pattern */
	error = iicbus_block_read(bus, 0x50, rbuf, 8, &rlen);
	if (error != 0 || rlen != 8)
		ok_r = 0;
	for (i = 0; i < 8; i++)
		if ((u_char)rbuf[i] != (u_char)(FAKEBB_TX_BASE + i))
			ok_r = 0;
	kprintf("[fakebb] read: error=%d read=%d "
	    "data=%02x%02x%02x%02x%02x%02x%02x%02x ok=%d\n",
	    error, rlen, (u_char)rbuf[0], (u_char)rbuf[1],
	    (u_char)rbuf[2], (u_char)rbuf[3], (u_char)rbuf[4],
	    (u_char)rbuf[5], (u_char)rbuf[6], (u_char)rbuf[7], ok_r);

	/* 3. write to a NACKed address: expect IIC_ENOACK + bus stopped */
	error = iicbus_block_write(bus, FAKEBB_NACK_ADDR, wbuf, 1, &sent);
	kprintf("[fakebb] nacked-write: error=%d (IIC_ENOACK=%d) "
	    "bus_started_after=%d\n", error, IIC_ENOACK, sc->started);

	/* 4. write byte + repeated start to read + stop (state machine) */
	byte = 0;
	error = iicbus_start(bus, 0x50, 10);
	if (error == 0)
		error = iicbus_write_byte(bus, 0x77, 10);
	if (error == 0)
		error = iicbus_repeated_start(bus, 0x51, 10);
	if (error == 0)
		error = iicbus_read_byte(bus, &byte, 10);
	if (error == 0)
		iicbus_stop(bus);
	kprintf("[fakebb] repstart-read: error=%d byte=0x%02x "
	    "(expect 0x%02x)\n", error, (u_char)byte, FAKEBB_TX_BASE);

	iicbus_release_bus(bus, dev);

	kprintf("[fakebb] SUMMARY: write_ok=%d read_ok=%d starts=%d "
	    "stops=%d rxlen=%d spray_sink=%d\n",
	    ok_w, ok_r, sc->starts, sc->stops, sc->rxlen, spray_sink);

	fakebb_sc0 = sc;
	sc->rxlen = 0;		/* fresh capture for DF-2310 */
	memset(sc->rx, 0, sizeof(sc->rx));

	return (0);
}


/* ------------------------------------------------------------------ */
/* DF-2310/DF-2311 additions: wire-capture dump + clear               */
/* ------------------------------------------------------------------ */

static int
fakebb3_dump_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct fakebb_softc *sc = fakebb_sc0;
	char hex[2 * 32 + 1];
	int i, error;

	if (sc == NULL)
		return (EINVAL);
	for (i = 0; i < 32 && i < sc->rxlen; i++)
		ksnprintf(hex + 2 * i, 3, "%02x", sc->rx[i]);
	hex[2 * (i > 0 ? i : 0)] = 0;
	error = sysctl_handle_string(oidp, hex, sizeof(hex), req);
	if (error == 0 && req->newptr != NULL) {
		kprintf("[fakebb3] rxlen=%d rx=%s\n", sc->rxlen, hex);
		fakebb3_dump_arm = 1;
	}
	return (error);
}

static int
fakebb3_clear_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = fakebb3_clear_arm;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error == 0 && req->newptr != NULL) {
		if (fakebb_sc0) {
			fakebb_sc0->rxlen = 0;
			memset(fakebb_sc0->rx, 0, sizeof(fakebb_sc0->rx));
		}
		fakebb3_clear_arm = v;
	}
	return (error);
}

SYSCTL_NODE(_hw, OID_AUTO, fakebb3, CTLFLAG_RW, 0, "fakebb DF-2310 rig");
SYSCTL_PROC(_hw_fakebb3, OID_AUTO, dumprx, CTLTYPE_STRING | CTLFLAG_RW,
    0, 0, fakebb3_dump_sysctl, "A", "slave RX capture (write anything to kprintf)");
SYSCTL_PROC(_hw_fakebb3, OID_AUTO, clearrx, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fakebb3_clear_sysctl, "I", "clear slave RX capture");


/* iicbus0 owner probe (DF-2311): reports struct iicbus_softc->owner */
static int
fakebb3_owner_sysctl(SYSCTL_HANDLER_ARGS)
{
	devclass_t dc;
	device_t bus;
	device_t owner = NULL;
	char buf[32];
	int error;

	dc = devclass_find("iicbus");
	bus = dc ? devclass_get_device(dc, 0) : NULL;
	if (bus) {
		/* struct iicbus_softc member order (iicbus.h:36-43):
		 * dev, owner, started -- owner at index 1 */
		struct iicbus_softc *sc = device_get_softc(bus);
		owner = sc->owner;
	}
	ksnprintf(buf, sizeof(buf), "%p", owner);
	error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
	if (error == 0 && req->newptr != NULL)
		kprintf("[fakebb3] iicbus0 owner=%p\n", owner);
	return (error);
}
SYSCTL_PROC(_hw_fakebb3, OID_AUTO, busowner, CTLTYPE_STRING | CTLFLAG_RW,
    0, 0, fakebb3_owner_sysctl, "A", "iicbus0 owner device (leak probe)");

static int
fakebb_detach(device_t dev)
{
	struct fakebb_softc *sc = device_get_softc(dev);
	device_t child;

	child = sc->iicbb;
	bus_generic_detach(dev);
	if (child)
		device_delete_child(dev, child);

	return (0);
}

static void
fakebb_child_detached(device_t dev, device_t child)
{
	struct fakebb_softc *sc = device_get_softc(dev);

	if (child == sc->iicbb)
		sc->iicbb = NULL;
}

static device_method_t fakebb_methods[] = {
	/* device interface */
	DEVMETHOD(device_identify,	fakebb_identify),
	DEVMETHOD(device_probe,		fakebb_probe),
	DEVMETHOD(device_attach,	fakebb_attach),
	DEVMETHOD(device_detach,	fakebb_detach),

	/* bus interface */
	DEVMETHOD(bus_child_detached,	fakebb_child_detached),

	/* iicbb interface (the fake GPIO vtable) */
	DEVMETHOD(iicbb_callback,	fakebb_callback),
	DEVMETHOD(iicbb_setsda,	fakebb_setsda),
	DEVMETHOD(iicbb_setscl,	fakebb_setscl),
	DEVMETHOD(iicbb_getsda,	fakebb_getsda),
	DEVMETHOD(iicbb_getscl,	fakebb_getscl),
	DEVMETHOD(iicbb_reset,		fakebb_reset),

	DEVMETHOD_END
};

static driver_t fakebb_driver = {
	"fakebb",
	fakebb_methods,
	sizeof(struct fakebb_softc),
};

static devclass_t fakebb_devclass;

DRIVER_MODULE(fakebb, nexus, fakebb_driver, fakebb_devclass, NULL, NULL);
MODULE_DEPEND(fakebb, iicbb, 1, 1, 1);
MODULE_VERSION(fakebb, 1);