DF-2210 / dfrig_nat.c
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); |