⬢ DragonFlyBSD Kernel Audit
DF-5642 / df5642_rig.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
/*
 * df5642_rig.c -- DF-5642/5643 rig: instantiate the REAL in-kernel
 * smbus driver (sys/bus/smbus/smbus.c, unmodified production code)
 * under a fake SMBus-controller parent and drive the newbus
 * attach/detach lifecycle from userland.
 *
 * Targets (sys/bus/smbus/smbus.c):
 *   DF-5642  smbus_attach() (smbus.c:94-97) creates the generic child
 *            and the whole address-probe child set UNCONDITIONALLY on
 *            every attach, while smbus_detach() (smbus.c:105-115) and
 *            the module teardown path (devclass_delete_driver,
 *            subr_bus.c:888-897) only DETACH children, never DELETE
 *            them.  Every detach+re-attach cycle of the smbus device
 *            leaks the full child set and mints duplicate remapped
 *            /dev/smbN[-XX] nodes (devclass_alloc_unit remap,
 *            subr_bus.c:1072-1081), breaking the 0x0400 unit-encoding
 *            contract (smb.c:134-141/229-231, cyapa.c:466-472).
 *   DF-5643  smbus_detach() never checks struct smbus_softc.owner
 *            (smbus.h:34): a bus owned by a client detaches "cleanly",
 *            device_detach() then FREES the softc (device_set_driver,
 *            subr_bus.c:1941-1946, called from subr_bus.c:2150) under
 *            (a) the owner -- whose next smbus_release_bus()
 *            (the smbioctl exit path, smb.c:356) derefs
 *            device_get_softc(bus)==NULL -> Fatal trap 12, and
 *            (b) parked SMB_WAIT requesters tsleep'ing ON the softc
 *            (smbconf.c:89/93) which are never woken and keep looping
 *            on freed memory.
 *
 * Rig structure (family standard, mirrors the DF-5559/5636 fakebb rigs):
 *
 *    nexus -> fakesmbb0 (this module: SMBus controller emulator
 *                        + /dev/df5642 ioctl surface)
 *                -> smbus0  (REAL in-kernel smbus driver, registered
 *                            on "fakesmbb" exactly like intpm.c:903
 *                            registers it on "intsmb"; created via
 *                            device_add_child+device_probe_and_attach
 *                            exactly like intpm.c:331-337)
 *                     -> smb children (REAL smb.ko, kldload'ed after
 *                        this rig; mints /dev/smbN[-XX] nodes)
 *
 * /dev/df5642 ioctls:
 *   STAT     list smbus0's children (count + nameunit each)
 *   CYCLE n  n x (device_detach(smbus0) + device_probe_and_attach(smbus0))
 *            -- the exact production APIs the module handlers invoke
 *   GRAB     smbus_request_bus(smbus0, fakesmbb0, SMB_DONTWAIT) -> owner
 *   VETO     device_detach(smbus0) WHILE OWNED -- must return EBUSY
 *   RELFREE  smbus_release_bus(smbus0, owner) after VETO freed the
 *            softc -- reproduces the smbioctl exit path (smb.c:356)
 *   PARK     kthread: smbus_request_bus(smbus0, nexus0, SMB_WAIT|NOINTR)
 *   WAKUAF   wakeup(freed softc) -- parked waiter resumes on freed mem
 */

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

#include <bus/smbus/smbconf.h>

/* struct smbus_softc (copied from sys/bus/smbus/smbus.h, family
 * standard: the header is tiny and private to the audit rig). */
struct smbus_softc {
	device_t owner;		/* smbus owner device structure */
};

#include "smbus_if.h"

/* df5642ctl.c <-> rig ioctl protocol */
#define DF5642_STAT	_IOWR('d', 1, int)
#define DF5642_CYCLE	_IOWR('d', 2, int)	/* arg = cycle count */
#define DF5642_GRAB	_IOWR('d', 3, int)
#define DF5642_VETO	_IOWR('d', 4, int)
#define DF5642_RELFREE	_IOWR('d', 5, int)
#define DF5642_PARK	_IOWR('d', 6, int)
#define DF5642_WAKUAF	_IOWR('d', 7, int)

static int df5642_open(struct dev_open_args *ap);
static int df5642_close(struct dev_close_args *ap);
static int df5642_ioctl(struct dev_ioctl_args *ap);

static struct dev_ops df5642_ops = {
	{ "df5642", 0, 0 },
	.d_open =	df5642_open,
	.d_close =	df5642_close,
	.d_ioctl =	df5642_ioctl,
};

struct fakesmbb_softc {
	device_t	self;		/* fakesmbb0: owner token */
	device_t	smbus;		/* smbus0: the bus under test */
	cdev_t		cdev;		/* /dev/df5642 */
	u_int		respond;	/* address mask the slave answers */
	/* parked-waiter state (DF-5643 run C) */
	volatile int	park_started;
	volatile int	park_done;
	int		park_error;
	void		*park_sc;	/* softc the waiter sleeps on */
};

/* address masks the emulated slave answers (addr 0..63 / 64..127) */
static uint64_t df5642_respond = ((uint64_t)1 << 0x10);	/* 0x10 */
static uint64_t df5642_respond_hi = (((uint64_t)1 << (0x50 - 64)) |
				      ((uint64_t)1 << (0x67 - 64)));

SYSCTL_NODE(_hw, OID_AUTO, df5642, CTLFLAG_RW, 0, "DF-5642 rig");
SYSCTL_ULONG(_hw_df5642, OID_AUTO, respond, CTLFLAG_RW,
    &df5642_respond, 0, "SMBus address mask lo (addr 0-63)");
SYSCTL_ULONG(_hw_df5642, OID_AUTO, respond_hi, CTLFLAG_RW,
    &df5642_respond_hi, 0, "SMBus address mask hi (addr 64-127)");

static int
fakesmbb_answers(int slave)
{

	if (slave < 0 || slave > 127)
		return (0);
	if (slave < 64)
		return ((df5642_respond >> slave) & 1);
	return ((df5642_respond_hi >> (slave - 64)) & 1);
}

/* ------------------------------------------------------------------ */
/* smbus_if methods: the emulated controller                           */
/* ------------------------------------------------------------------ */

static int
fakesmbb_callback(device_t dev, int index, void *data)
{

	(void)dev;
	(void)data;
	if (index == SMB_REQUEST_BUS || index == SMB_RELEASE_BUS)
		return (0);
	return (SMB_EINVAL);
}

static int
fakesmbb_trans(device_t dev, int slave, char cmd, int op,
    char *wbuf, int wcount, char *rbuf, int rcount, int *actualp)
{
	struct fakesmbb_softc *sc = (struct fakesmbb_softc *)device_get_softc(dev);

	(void)cmd;
	(void)op;
	(void)wbuf;
	(void)wcount;
	(void)sc;

	if (fakesmbb_answers(slave)) {
		if (rcount > 0 && rbuf != NULL)
			rbuf[0] = (char)0x5a;
		if (actualp != NULL)
			*actualp = (rcount > 0) ? 1 : 0;
		return (0);
	}
	return (SMB_ENOACK);
}

static void
fakesmbb_intr(device_t dev, u_char devaddr, char low, char high, int error)
{

	(void)dev; (void)devaddr; (void)low; (void)high; (void)error;
}

/* ------------------------------------------------------------------ */
/* helpers                                                             */
/* ------------------------------------------------------------------ */

static void
stat_children(device_t bus, const char *tag)
{
	device_t *list;
	int n = 0, i, error;

	error = device_get_children(bus, &list, &n);
	if (error) {
		kprintf("[df5642] %s: device_get_children error=%d\n", tag, error);
		return;
	}
	kprintf("[df5642] %s: nchildren=%d\n", tag, n);
	for (i = 0; i < n; i++) {
		const char *nu = device_get_nameunit(list[i]);

		kprintf("[df5642] %s: child[%d] = %s (unit=%d state=%d)\n",
		    tag, i, nu ? nu : "(unnamed)",
		    device_get_unit(list[i]), device_get_state(list[i]));
	}
	kfree(list, M_TEMP);
}

/* DF-5643 run C: kthread parking in smbus_request_bus(SMB_WAIT) */
static void
park_thread(void *arg)
{
	struct fakesmbb_softc *sc = arg;
	device_t bus = sc->smbus;
	device_t token = device_get_parent(sc->self);	/* nexus0: non-owner */

	sc->park_sc = device_get_softc(bus);
	kprintf("[df5642] PARK-T: calling request(nexus0, WAIT|NOINTR) "
	    "softc=%p\n", sc->park_sc);
	sc->park_started = 1;
	wakeup(&sc->park_started);

	sc->park_error = smbus_request_bus(bus, token, SMB_WAIT | SMB_NOINTR);
	kprintf("[df5642] PARK-T: request RETURNED %d (post-free resume)\n",
	    sc->park_error);
	sc->park_done = 1;
	wakeup(&sc->park_done);
	kthread_exit();
}

/* ------------------------------------------------------------------ */
/* /dev/df5642                                                         */
/* ------------------------------------------------------------------ */

static int
df5642_open(struct dev_open_args *ap)
{

	(void)ap;
	return (0);
}

static int
df5642_close(struct dev_close_args *ap)
{

	(void)ap;
	return (0);
}

static int
df5642_ioctl(struct dev_ioctl_args *ap)
{
	cdev_t cdev = ap->a_head.a_dev;
	struct fakesmbb_softc *sc = cdev->si_drv1;
	struct smbus_softc *ssc;
	u_long cmd = ap->a_cmd;
	int arg = *(int *)ap->a_data;
	int error, i;

	if (sc->smbus == NULL) {
		sc->smbus = devclass_get_device(devclass_find("smbus"), 0);
		if (sc->smbus == NULL)
			return (ENXIO);
	}
	ssc = (struct smbus_softc *)device_get_softc(sc->smbus);

	switch (cmd) {
	case DF5642_STAT:
		kprintf("[df5642] STAT: smbus0=%p softc=%p owner=%p\n",
		    (void *)sc->smbus, (void *)ssc,
		    ssc ? (void *)ssc->owner : NULL);
		stat_children(sc->smbus, "STAT");
		*(int *)ap->a_data = 0;
		return (0);

	case DF5642_CYCLE:
		for (i = 0; i < arg; i++) {
			error = device_detach(sc->smbus);
			kprintf("[df5642] CYCLE %d.%d: device_detach(smbus0)=%d\n",
			    i, 0, error);
			if (error)
				break;
			error = device_probe_and_attach(sc->smbus);
			kprintf("[df5642] CYCLE %d.%d: device_probe_and_attach=%d\n",
			    i, 1, error);
			if (error)
				break;
			stat_children(sc->smbus, "after-cycle");
		}
		*(int *)ap->a_data = error;
		return (0);

	case DF5642_GRAB:
		error = smbus_request_bus(sc->smbus, sc->self, SMB_DONTWAIT);
		ssc = (struct smbus_softc *)device_get_softc(sc->smbus);
		kprintf("[df5642] GRAB: request(fakesmbb0, DONTWAIT)=%d "
		    "owner=%p (caller=%p)\n", error,
		    ssc ? (void *)ssc->owner : NULL, (void *)sc->self);
		*(int *)ap->a_data = error;
		return (0);

	case DF5642_VETO:
		/* detach WHILE a client verifiably owns the bus */
		ssc = (struct smbus_softc *)device_get_softc(sc->smbus);
		kprintf("[df5642] VETO: pre-detach owner=%p (must be "
		    "non-NULL)\n", ssc ? (void *)ssc->owner : NULL);
		error = device_detach(sc->smbus);
		kprintf("[df5642] VETO: device_detach(smbus0)=%d "
		    "%s\n", error,
		    (error == 0) ? "<<< BUS DETACHED UNDER ITS OWNER "
		    "(must be EBUSY=16)" : "(vetoed: GOOD)");
		kprintf("[df5642] VETO: post-detach softc=%p "
		    "(NULL = freed under the owner)\n",
		    (void *)device_get_softc(sc->smbus));
		*(int *)ap->a_data = error;
		return (0);

	case DF5642_RELFREE:
		/* the owner's release path = smbioctl exit (smb.c:356) */
		kprintf("[df5642] RELFREE: calling smbus_release_bus(smbus0, "
		    "fakesmbb0) with softc=%p ...\n",
		    (void *)device_get_softc(sc->smbus));
		error = smbus_release_bus(sc->smbus, sc->self);
		kprintf("[df5642] RELFREE: returned %d\n", error);
		*(int *)ap->a_data = error;
		return (0);

	case DF5642_PARK:
		sc->park_started = 0;
		sc->park_done = 0;
		error = kthread_create(park_thread, sc, NULL, "df5642_park");
		if (error) {
			kprintf("[df5642] PARK: kthread_create=%d\n", error);
			*(int *)ap->a_data = error;
			return (0);
		}
		for (i = 0; i < 30 && !sc->park_started; i++)
			tsleep(&sc->park_started, 0, "df42s", hz / 10);
		kprintf("[df5642] PARK: waiter started=%d done=%d "
		    "(parked on softc=%p)\n", sc->park_started,
		    sc->park_done, sc->park_sc);
		*(int *)ap->a_data = sc->park_started;
		return (0);

	case DF5642_WAKUAF:
		kprintf("[df5642] WAKUAF: done=%d; wakeup(freed sc=%p)\n",
		    sc->park_done, sc->park_sc);
		if (sc->park_sc != NULL)
			wakeup(sc->park_sc);
		for (i = 0; i < 20 && !sc->park_done; i++)
			tsleep(&sc->park_done, 0, "df42w", hz / 10);
		kprintf("[df5642] WAKUAF: after wakeup: done=%d error=%d "
		    "(done=0 -> waiter re-parked on FREED softc)\n",
		    sc->park_done, sc->park_error);
		*(int *)ap->a_data = sc->park_done;
		return (0);

	default:
		return (ENOTTY);
	}
}

/* ------------------------------------------------------------------ */
/* fakesmbb bus device plumbing                                        */
/* ------------------------------------------------------------------ */

static void
fakesmbb_identify(driver_t *driver, device_t parent)
{

	if (device_find_child(parent, "fakesmbb", 0) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "fakesmbb", 0);
}

static int
fakesmbb_probe(device_t dev)
{

	device_set_desc(dev, "DF-5642 fake SMBus controller");
	return (0);
}

static int
fakesmbb_attach(device_t dev)
{
	struct fakesmbb_softc *sc = (struct fakesmbb_softc *)device_get_softc(dev);

	sc->self = dev;

	/* mirror intpm.c:331-337 exactly */
	sc->smbus = device_add_child(dev, "smbus", -1);
	if (sc->smbus == NULL) {
		kprintf("[df5642] failed to add smbus child\n");
		return (ENXIO);
	}
	if (device_probe_and_attach(sc->smbus) != 0)
		kprintf("[df5642] smbus child failed to attach\n");

	sc->cdev = make_dev(&df5642_ops, 0, UID_ROOT, GID_WHEEL, 0600,
	    "df5642");
	if (sc->cdev == NULL)
		kprintf("[df5642] make_dev failed\n");
	else
		sc->cdev->si_drv1 = sc;

	kprintf("[df5642] attach done: smbus0=%p cdev=%p "
	    "(now: kldload smb)\n", (void *)sc->smbus, (void *)sc->cdev);
	stat_children(sc->smbus, "attach");
	return (0);
}

static int
fakesmbb_detach(device_t dev)
{
	struct fakesmbb_softc *sc = (struct fakesmbb_softc *)device_get_softc(dev);
	device_t child = sc->smbus;

	if (sc->cdev) {
		dev_ops_remove_minor(&df5642_ops, 0);
		sc->cdev = NULL;
	}
	bus_generic_detach(dev);
	if (child)
		device_delete_child(dev, child);
	return (0);
}

static device_method_t fakesmbb_methods[] = {
	/* device interface */
	DEVMETHOD(device_identify,	fakesmbb_identify),
	DEVMETHOD(device_probe,		fakesmbb_probe),
	DEVMETHOD(device_attach,	fakesmbb_attach),
	DEVMETHOD(device_detach,	fakesmbb_detach),

	/* smbus interface (the emulated controller vtable) */
	DEVMETHOD(smbus_callback,	fakesmbb_callback),
	DEVMETHOD(smbus_trans,		fakesmbb_trans),
	DEVMETHOD(smbus_intr,		fakesmbb_intr),

	DEVMETHOD_END
};

static driver_t fakesmbb_driver = {
	"fakesmbb",
	fakesmbb_methods,
	sizeof(struct fakesmbb_softc),
};

static devclass_t fakesmbb_devclass;

DRIVER_MODULE(fakesmbb, nexus, fakesmbb_driver, fakesmbb_devclass, NULL, NULL);
/* register the REAL production smbus driver on the fake bus
 * (exactly what intpm.c:903 does for the PIIX4) */
DRIVER_MODULE(smbus, fakesmbb, smbus_driver, smbus_devclass, NULL, NULL);
MODULE_DEPEND(fakesmbb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
MODULE_VERSION(fakesmbb, 1);