⬢ DragonFlyBSD Kernel Audit
DF-2210 / dfrig_nat.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
/*
 * dfrig_nat.c -- DF-2210 verification rig
 *                (sys/dev/disk/nata/chipsets/ata-national.c:77-78)
 *
 * Finding under test:
 *
 *	ata_national_setmode() unconditionally executes
 *
 *	    ch->dma->alignment = 16;          <-- ata-national.c:77
 *	    ch->dma->max_iosize = 64*DEV_BSIZE; <-- ata-national.c:78
 *
 *	with no NULL guard, while its own template (the Cyrix original,
 * chipsets/ata-cyrix.c:82-101) guards the identical derefs behind
 * `if (ctlr->r_res1)`.  ch->dma is legitimately NULL in production:
 * ata-dma.c:153-154 frees ch->dma and sets it to NULL whenever DMA
 * allocation fails ("WARNING - DMA allocation failed, disabling DMA"),
 * after which the channel keeps operating in PIO -- and every later
 * setmode dispatch panics the kernel instead.
 *
 * Real dispatch chain that reaches the handler (all production cites):
 *
 *   ioctl(IOCATASMODE)  sys/dev/disk/nata/ata-all.c:523-526
 *	ATA_SETMODE(parent, dev)                     (newbus method, ata_if.m)
 *   -> ata_pcichannel_setmode()  sys/dev/disk/nata/ata-pci.c:855-866
 *	ctlr->setmode(dev, ATA_PIO_MAX);             <-- extracted call site
 *	if (mode >= ATA_DMA) ctlr->setmode(dev, mode);
 *   (same funnel for boot reinit and acd_open -> atapi-cd.c:147/226,
 *    atapi-tape.c:121/214, atapi-fd.c:107/180, ata-disk.c:436)
 *   -> ata_national_setmode()  chipsets/ata-national.c:60
 *   -> line 77 NULL deref: write 16 to absolute vaddr 0x58
 *      (offsetof(struct ata_dma, alignment) == 0x58 on amd64;
 *       max_iosize sits at 0x5c)
 *
 * The hardware is gated (National Geode SC1100, PCI ID 0x0502100b), so
 * this rig follows the established production-handler dispatch class
 * (DF-252x trm, DF-249x nata):
 *
 *  - the production file chipsets/ata-national.c is #included VERBATIM
 *    from the guest /usr/src tree (build.sh verifies the file sha256
 *    against the audited sys/ tree).  The ONLY textual intervention is
 *    `#define ata_national_ident rig_unused_ata_national_ident`, which
 *    renames the never-called ident() entry point to avoid a duplicate
 *    global against the built-in kernel copy; ata_national_setmode
 *    itself is compiled byte-for-byte as written, static, under its
 *    production name (backtrace below reads `ata_national_setmode+..`).
 *
 *  - the RUNNING KERNEL's OWN ata_national_setmode (local symbol, from
 *    `nm /boot/kernel/kernel`, address injected via sysctl) is ALSO
 *    dispatched -- the shipped binary, zero recompilation.
 *
 *  - the channel/controller/ata-device state is exactly what the
 *    machine presents when DMA is unavailable: ch->dma == NULL, disk
 *    present on the channel (ch->devices = ATA_ATA_MASTER), PIO4/UDMA2
 *    capable identify data in atadev->param.  The channel is in the
 *    ATA_STALL_QUEUE state with a begin_transaction that reports the
 *    drive accepting SETXFER, so the kernel's own production
 *    ata_controlcmd()/ata_queue_request() (ata-queue.c:158-190,
 *    synchronous CONTROL completion at ata-queue.c:100-115) run
 *    unmodified.  The PCI config timing writes at ata-national.c:88-100
 *    land on a fabricated kobj bus that records them (the production
 *    code calls the pcivar.h static inlines PCI_WRITE_CONFIG /
 *    PCI_READ_CONFIG; those dispatch through the kernel kobj machinery
 *    on our fabricated ops table).
 *
 * Tests (sysctl kern.dfrig_nat.go=N):
 *
 *   1  THE FINDING, shipped kernel handler: ch->dma=NULL, direct call
 *      of the kernel's own ata_national_setmode with mode=ATA_PIO_MAX
 *      (exactly the first call of the extracted call site).
 *      STOCK KERNEL: fatal trap 12 page fault, vad=0x58, stopped at
 *      ata_national_setmode+0x..: movl $0x10,0x58(%rax)  [line 77].
 *   2  CONTROL, shipped kernel handler: same but ch->dma=&fake_dma ->
 *      normal setmode: alignment 2->16, max_iosize 65536->32768,
 *      PCI timing writes at 0x44/0x40 recorded, atadev->mode set.
 *   3  THE FINDING, verbatim rig copy: dispatch the #included
 *      production source's own ata_national_setmode, ch->dma=NULL.
 *      STOCK KERNEL: identical panic (byte-identity of the copy).
 *   4  CONTROL, verbatim rig copy, ch->dma=&fake_dma.
 *   5  FULL extracted call site (ata-pci.c:855-866): ctlr->setmode via
 *      the controller softc, requested mode ATA_UDMA2 (a UDMA disk at
 *      boot), ch->dma=NULL.  STOCK KERNEL: panic on the first
 *      ctlr->setmode call.  FIXED KERNEL: both calls complete (PIO
 *      timings for call 1, udmatiming[2] for call 2).
 *   6  CONTROL, full extracted call site, ch->dma=&fake_dma.
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/bus_dma.h>
#include <sys/bus_resource.h>
#include <sys/callout.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/nata.h>
#include <sys/queue.h>
#include <sys/rman.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/bus_private.h>	/* struct bsd_device layout */

#include <bus/pci/pcireg.h>
#include <bus/pci/pcivar.h>

#include "ata-all.h"
#include "ata-pci.h"

/* ------------------------------------------------------------------ */
/* CHECKER NOTE: the production file below is included VERBATIM.       */
/* The single #define renames only the never-called ident() symbol so  */
/* this KLD does not collide with the built-in kernel copy; the        */
/* setmode function under test keeps its production name and body.     */
/* build.sh pins the file by sha256 before compiling.                  */
/* ------------------------------------------------------------------ */
int rig_unused_ata_national_ident(device_t dev);	/* kproto */
#define ata_national_ident rig_unused_ata_national_ident
#include "chipsets/ata-national.c"	/* <-- UNMODIFIED production code */
#undef ata_national_ident

/* ------------------------------------------------------------------ */
/* channel HW completion fabrication: with ch->state == ATA_STALL_QUEUE */
/* the PRODUCTION ata_queue_request() (ata-queue.c:100-115) executes   */
/* ATA_R_CONTROL requests synchronously via ch->hw.begin_transaction   */
/* and returns immediately when it reports ATA_OP_FINISHED -- no       */
/* queueing, no tsleep, no interrupt.  This is the state a channel is  */
/* in while reinit/setmode choreography runs.  Our begin_transaction   */
/* stands for the drive accepting SET FEATURES / SETXFER.  With this   */
/* in place even the RUNNING KERNEL's own ata_controlcmd() and         */
/* ata_queue_request() (ata-queue.c:158-190/69-157) run unmodified.    */
/* ------------------------------------------------------------------ */
static int rig_cmd_seen;		/* begin_transaction dispatches */

static int
rig_begin_transaction(struct ata_request *request)
{
	rig_cmd_seen++;
	request->result = 0;			/* drive accepted SETXFER */
	request->flags |= ATA_R_COMPLETED;
	return ATA_OP_FINISHED;
}

/* ------------------------------------------------------------------ */
/* fabricated kobj bus: the PCI config timing writes of the production */
/* handler (ata-national.c:88-100) dispatch through the kernel kobj    */
/* machinery on this ops table and get recorded                         */
/* ------------------------------------------------------------------ */
static int rig_pci_n;
static struct { int reg; u_int32_t val; int width; } rig_pci_wr[16];
static int rig_pci_reads;
static int rig_i;

static u_int32_t rig_pci_read(device_t bus, device_t child, int reg,
			      int width);
static void rig_pci_write(device_t bus, device_t child, int reg,
			  u_int32_t val, int width);

static struct kobj_method rig_pci_methods[] = {
	{ &pci_read_config_desc,  (kobjop_t)rig_pci_read },
	{ &pci_write_config_desc, (kobjop_t)rig_pci_write },
	{ NULL, NULL }
};
static struct kobj_class rig_bus_class = {
	"dfrig_nat_pcib", rig_pci_methods, 0, NULL, 0, NULL
};
static struct kobj_ops rig_bus_ops;
static struct kobj_method rig_dummy_method = { NULL, NULL };

static u_int32_t
rig_pci_read(device_t bus, device_t child, int reg, int width)
{
	rig_pci_reads++;
	return 0;		/* config reads as zero (like a fresh chip) */
}

static void
rig_pci_write(device_t bus, device_t child, int reg, u_int32_t val,
	      int width)
{
	if (rig_pci_n < 16) {
		rig_pci_wr[rig_pci_n].reg = reg;
		rig_pci_wr[rig_pci_n].val = val;
		rig_pci_wr[rig_pci_n].width = width;
	}
	rig_pci_n++;
}

/* ------------------------------------------------------------------ */
/* fake device graph -- the exact graph nata hands the chipset driver  */
/* when a disk sits on the channel of an SC1100 controller whose DMA   */
/* could not be allocated:                                             */
/*                                                                     */
/*   rig_bus (kobj ops) <- fake_pci_dev (gparent, ctlr softc)          */
/*        <- fake_ch_dev (struct ata_channel, dma==NULL)               */
/*        <- fake_ata_dev (struct ata_device, unit=ATA_MASTER)         */
/* ------------------------------------------------------------------ */
static struct bsd_device fake_bus_dev, fake_pci_dev, fake_ch_dev;
static struct bsd_device fake_ata_dev;
static struct ata_channel fake_ch;
static struct ata_device fake_atadev;
static struct ata_dma fake_dma;
static struct ata_pci_controller fake_ctlr;

static unsigned long rig_kern_addr;	/* kernel own handler, via nm */
static int rig_armed;

static void
rig_setup_graph(void)
{
	int i;

	bzero(&fake_bus_dev, sizeof(fake_bus_dev));
	bzero(&fake_pci_dev, sizeof(fake_pci_dev));
	bzero(&fake_ch_dev, sizeof(fake_ch_dev));
	bzero(&fake_ata_dev, sizeof(fake_ata_dev));
	bzero(&fake_ch, sizeof(fake_ch));
	bzero(&fake_atadev, sizeof(fake_atadev));
	bzero(&fake_ctlr, sizeof(fake_ctlr));

	/* kobj ops cache pre-filled so the kernel kobj lookup (slow path
	 * through rig_bus_class.methods) can run without touching real hw */
	for (i = 0; i < KOBJ_CACHE_SIZE; i++)
		rig_bus_ops.cache[i] = &rig_dummy_method;
	rig_bus_ops.cls = &rig_bus_class;
	fake_bus_dev.ops = (kobj_ops_t)&rig_bus_ops;

	/* pci <- channel <- ata device, exactly as GRANDPARENT walks it */
	fake_pci_dev.parent = &fake_bus_dev;
	fake_pci_dev.softc = &fake_ctlr;
	fake_ch_dev.parent = &fake_pci_dev;
	fake_ch_dev.softc = &fake_ch;
	fake_ata_dev.parent = &fake_ch_dev;
	fake_ata_dev.softc = &fake_atadev;

	/* channel: DMA unavailable is the whole point (set by caller) */
	fake_ch.dev = &fake_ch_dev;
	fake_ch.unit = 0;
	fake_ch.devices = ATA_ATA_MASTER;	/* disk present */
	fake_ch.state = ATA_STALL_QUEUE;	/* sync CONTROL completion */
	fake_ch.hw.begin_transaction = rig_begin_transaction;
	lockinit(&fake_ch.state_mtx, "dfrig_nat_st", 0, LK_CANRECURSE);
	lockinit(&fake_ch.queue_mtx, "dfrig_nat_q", 0, LK_CANRECURSE);

	/* ata device: master, PIO4/UDMA2 capable disk identify data */
	fake_atadev.dev = &fake_ata_dev;
	fake_atadev.unit = ATA_MASTER;
	fake_atadev.mode = 0xff;
	fake_atadev.param.atavalid = ATA_FLAG_64_70;
	fake_atadev.param.apiomodes = 0x02;	/* PIO4 (ata_pmode -> 0x0c) */
	fake_atadev.param.atavalid |= ATA_FLAG_88;
	fake_atadev.param.udmamodes = 0x04;	/* UDMA2 (ata_umode -> 0x42) */
}

/* one dispatch of the handler under test */
static void
rig_dispatch(int via_kernel, int dma_null, const char *tag)
{
	/* production ata-dmainit defaults (ata-dma.c:75-83) so the control
	 * run shows the chipset OVERRIDING them, as on real hardware */
	bzero(&fake_dma, sizeof(fake_dma));
	fake_dma.alignment = 2;
	fake_dma.max_iosize = 128 * DEV_BSIZE;

	fake_ch.dma = dma_null ? NULL : &fake_dma;
	fake_atadev.mode = 0xff;
	rig_pci_n = 0;
	rig_pci_reads = 0;
	rig_cmd_seen = 0;
	bzero(rig_pci_wr, sizeof(rig_pci_wr));

	kprintf("dfrig_nat[%s]: BEFORE dispatch: ch=%p ch->dma=%p "
		"(NULL means DMA unavailable, ata-dma.c:153) atadev->unit=%d\n",
		tag, &fake_ch, fake_ch.dma, fake_atadev.unit);
	kprintf("dfrig_nat[%s]: dispatching production ata_national_setmode"
		"(dev, ATA_PIO_MAX) %s\n", tag,
		via_kernel ? "(RUNNING KERNEL own copy)" :
		"(verbatim #included copy)");

	if (via_kernel)
		((void (*)(device_t, int))rig_kern_addr)(&fake_ata_dev,
							ATA_PIO_MAX);
	else
		ata_national_setmode(&fake_ata_dev, ATA_PIO_MAX);

	kprintf("dfrig_nat[%s]: RETURNED (no panic): ch->dma=%p "
		"alignment=%u max_iosize=%u atadev->mode=0x%02x "
		"controlcmd_seen=%d pci_reads=%d\n",
		tag, fake_ch.dma, fake_dma.alignment, fake_dma.max_iosize,
		fake_atadev.mode, rig_cmd_seen, rig_pci_reads);
	for (rig_i = 0; rig_i < rig_pci_n && rig_i < 16; rig_i++)
		kprintf("dfrig_nat[%s]:   pci_write_config(gparent, "
			"0x%02x, 0x%08x, %d)\n", tag, rig_pci_wr[rig_i].reg,
			rig_pci_wr[rig_i].val, rig_pci_wr[rig_i].width);
}

/* extracted call site: ata_pcichannel_setmode(), ata-pci.c:855-866,
 * verbatim extraction (static in the kernel, cannot be linked) */
static void
rig_extracted_callsite(device_t dev, int mode)
{
	struct ata_pci_controller *ctlr = device_get_softc(GRANDPARENT(dev));

	ctlr->setmode(dev, ATA_PIO_MAX);
	if (mode >= ATA_DMA)
		ctlr->setmode(dev, mode);
}

static void
rig_dispatch_full(int via_kernel, int dma_null, const char *tag)
{
	bzero(&fake_dma, sizeof(fake_dma));
	fake_dma.alignment = 2;
	fake_dma.max_iosize = 128 * DEV_BSIZE;

	fake_ch.dma = dma_null ? NULL : &fake_dma;
	fake_atadev.mode = ATA_UDMA2;	/* requested mode, as at ioctl */
	rig_pci_n = 0;
	rig_pci_reads = 0;
	rig_cmd_seen = 0;
	bzero(rig_pci_wr, sizeof(rig_pci_wr));
	fake_ctlr.setmode = via_kernel ?
		(void (*)(device_t, int))rig_kern_addr : ata_national_setmode;

	kprintf("dfrig_nat[%s]: BEFORE: ch->dma=%p requested mode=ATA_UDMA2 "
		"-- full extracted call site ata-pci.c:855-866\n",
		tag, fake_ch.dma);

	rig_extracted_callsite(&fake_ata_dev, fake_atadev.mode);

	kprintf("dfrig_nat[%s]: RETURNED (no panic): alignment=%u "
		"max_iosize=%u atadev->mode=0x%02x controlcmd_seen=%d\n",
		tag, fake_dma.alignment, fake_dma.max_iosize,
		fake_atadev.mode, rig_cmd_seen);
	for (rig_i = 0; rig_i < rig_pci_n && rig_i < 16; rig_i++)
		kprintf("dfrig_nat[%s]:   pci_write_config(gparent, "
			"0x%02x, 0x%08x, %d)\n", tag, rig_pci_wr[rig_i].reg,
			rig_pci_wr[rig_i].val, rig_pci_wr[rig_i].width);
}

/* ------------------------------------------------------------------ */
/* sysctl plumbing                                                     */
/* ------------------------------------------------------------------ */
static int
rig_go_sysctl(SYSCTL_HANDLER_ARGS)
{
	int val = 0, error;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error || !req->newptr)
		return error;

	rig_armed = 1;
	switch (val) {
	case 1:
		rig_dispatch(1, 1, "go1");
		break;			/* never reached on stock kernel */
	case 2:
		rig_dispatch(1, 0, "go2");
		break;
	case 3:
		rig_dispatch(0, 1, "go3");
		break;			/* never reached on stock kernel */
	case 4:
		rig_dispatch(0, 0, "go4");
		break;
	case 5:
		rig_dispatch_full(1, 1, "go5");
		break;			/* never reached on stock kernel */
	case 6:
		rig_dispatch_full(1, 0, "go6");
		break;
	default:
		return EINVAL;
	}
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, dfrig_nat, CTLFLAG_RW, 0,
		   "DF-2210 ata-national rig");

SYSCTL_ULONG(_kern_dfrig_nat, OID_AUTO, addr, CTLFLAG_RW, &rig_kern_addr, 0,
	     "address of the kernel own ata_national_setmode (from nm)");

SYSCTL_PROC(_kern_dfrig_nat, OID_AUTO, go, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, rig_go_sysctl, "I", "fire a scenario");

/* ------------------------------------------------------------------ */
/* module                                                              */
/* ------------------------------------------------------------------ */
static int
dfrig_nat_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		rig_setup_graph();
		kprintf("dfrig_nat: DF-2210 rig loaded "
			"(production chipsets/ata-national.c:77-78 included "
			"verbatim; kernel handler addr=0x%lx)\n",
			rig_kern_addr);
		return 0;
	case MOD_UNLOAD:
		if (rig_armed)
			return EBUSY;
		lockuninit(&fake_ch.state_mtx);
		lockuninit(&fake_ch.queue_mtx);
		return 0;
	default:
		return 0;
	}
}

static moduledata_t dfrig_nat_mod = { "dfrig_nat", dfrig_nat_load, 0 };
DECLARE_MODULE(dfrig_nat, dfrig_nat_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);