DF-5648 verdict narrative — pass 2 audit of sys/bus/pci/isa_pci.c ================================================================= FINDING (Info, certain): `isab` (this file) and `acpi_isab` share `isab_devclass` (declared sys/bus/pci/isa_pci.c:84, registered into by DRIVER_MODULE at isa_pci.c:86 and by acpi_isab at sys/dev/acpica/acpi_isab.c:89-90). `isab_attach` — the attach method wired by THIS file at isa_pci.c:60 and implemented at sys/bus/isa/isa_common.c:1046-1054 — hardcodes a *wired* unit: `device_add_child(dev, "isa", 0)`. `acpi_isab_probe` (acpi_isab.c:93-105) will attach an ISA bus to ANY ACPI device whose _HID/_CID is PNP0A05/PNP0A06 — including QEMU's Processor Container `\_SB_.CPUS` (_HID=ACPI0010, _CID=PNP0A06), whose only guard is that it must become unit 0 (`devclass_get_device(isab_devclass, 0) != dev`, acpi_isab.c:99). When ACPI enumeration of \_SB_.CPUS precedes PCI enumeration of the PIIX3 (deterministic on the audit rig), the container takes isab0→isa0, and the REAL PCI-ISA bridge matched by isab_probe (class 0x0601, isa_pci.c:100-102; devid 0x70008086 also in the compat table at isa_pci.c:113) is silently renumbered isab1→isa1 by devclass_alloc_unit's "using next available unit number" path (sys/kern/subr_bus.c:1072-1081, subr_bus.c:1139). REPRODUCED (status=reproduced, impact=none): observed live on the audit guest across two independent boots — see run.log: isab0: on acpi0 <- container stole unit 0 isa0: on isab0 <- phantom, zero children isab1: at device 1.0 on pci0 <- this file's driver isa1: on isab1 <- real bus (vga0, orm0) devinfo confirms isab1@pci0:0:1:0 chip=0x70008086 class=0x060100 with children vga0/orm0 under isa1, and an EMPTY isa0 under the container. Negative control (run.2.log): injected `hint.foo.0.at="isa"` into /boot/loader.conf, rebooted — no `foo` child appears on either bus. Root cause: isahint_identify's generic loop (sys/bus/isa/isahint.c:121-129) enumerates via resource_query_string, which walks only the compiled-in static devtab (subr_bus.c:2402-2412); kenv hints are only consumed by point lookups (resource_kenv, subr_bus.c:2300-2325). The X86_64_GENERIC kernel compiles in no hints (devtab empty), so on the stock kernel loader hints are enumeration-inert. This neutralizes the double-enumeration hazard on GENERIC (good), but on hint-compiled custom kernels the hazard is fully live: `at isa` hints would be enumerated on BOTH isa0 (phantom) and isa1 (real), each BUS_ADD_CHILD renumbering to the next free unit (isahint.c:55, subr_bus.c:1079-1080) → two device instances for the same physical hardware; shareable legacy IRQs can be registered by both instances. `at isa0`-targeted hints would misdirect to the resourceless phantom bus. orm/vga identify hooks have rescan guards only (sys/dev/misc/orm/orm.c:88-93), not first-bus guards — rman exclusivity is what actually prevented double-attach of the ROM/VGA resources on this rig. WHY INFO / impact=none: every input driving this topology (ACPI namespace contents, PCI config space, loader hints/kernel hints) is firmware- or boot-config-controlled = root-equivalent influence; there is no unprivileged local, remote, or crafted-image attack path. On the default rig the observable consequence is purely cosmetic unit numbering (system is fully functional at isa1 — proven live). EXPLOIT CHAIN: (empty — no attacker-reachable path; this is a robustness/hardening finding.) PASS-2 CHECKLIST (file sys/bus/pci/isa_pci.c, 150 LOC) — everything else clean: (a) probe/attach + resource forwarding: probe consumes only fixed-width config reads (pci_get_class/subclass/devid — 8/8/32-bit, isa_pci.c:100-110); attach is the trivial child-add in isa_common.c:1046-1054; all bus_* methods are pure bus_generic_* pass-through to the PCI parent (isa_pci.c:68-73); the driver allocates/claims no resources of its own. (b) detach-veto family (DF-5544/5562/5598): DEVMETHOD(device_detach, bus_generic_detach) at isa_pci.c:61 → subr_bus.c:2872-2877 propagates the first failing child detach and stops; isab owns no softc (driver_t size 0, isa_pci.c:81), no IRQ/IO/DMA to tear down under a vetoed child. NOT an instance of the family — this is the safe idiom. (c) ivar/accessor: no ivar methods in isab; pci accessors used are read-only; no child ivar writes. (The grandchild-ivar cast in isa.c is DF-5641, not here.) (d) config-space-supplied sizes: none — no BAR parsing, no ranges decoding, no length math anywhere in the file; the devid table (isa_pci.c:110-133) is compile-time constants. Also killed: kprintf of 8-bit subclass (isa_pci.c:134-136) is no info leak (config space is user-readable via pciconf anyway); module unload path standard; probe ordering (-10000 vs acpi_isab's 0) is cross-bus and unexploitable; the theoretical devclass-slot TOCTOU in acpi_isab.c:99 cannot corrupt anything because devclass_add_device renumbers collisions safely (subr_bus.c:1072-1081). FIX VALIDATION: not_testable — impact=none on the stock kernel (negative control proves the hazard path is enumeration-inert on GENERIC), so a fix build has no observable security behavior to flip; fix.diff targets the only consequential variant (double enumeration on hint-compiled kernels) with a first-bus guard in isahint_identify. Recommended complementary hardening (prose only): have acpi_isab refuse Processor Containers (_HID=ACPI0010) unless they declare ISA-enumerable children, or make isab_attach log the renumber loudly at boot. Guest left CLEAN: `vm.sh reset with-src` performed after evidence capture (loader.conf experiment reverted; guest_dirty=0). ## Fix status (batch validation) Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.