β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5468

vga_pci_map_bios() ROM path is dead on spec-conformant devices: lazy pci_alloc_map() misclassifies the ROM BAR as I/O because the enable bit survives the sizing readback

Summary

DF never pre-reserves the ROM BAR at enumeration, so the lazy sizing dance reads back base_mask|enable (bit0 RW per spec - empirically ffff0001) and PCI_BAR_MEM(testval) is false: the SYS_RES_MEMORY request is rejected as ioport and map_bios returns NULL - non-boot-display VBIOS reads by drm ALWAYS fail. Guest-probed live (readback ffff0001, unseeded map attempts fail). Fails closed; mask-bit0-or-prereserve fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5468 Β· 5 files
FileTypeDescriptionSize
rombar_probe.sh β€” 1.2 KB view raw
run.log β€” 362 B view raw
README.md β€” 3.0 KB ↓ raw
manifest.json β€” 955 B view raw
verdict.json β€” 3.4 KB view raw

DF-5468 β€” vga_pci_map_bios() ROM path is dead: lazy pci_alloc_map()

misclassifies ROM BARs whose enable bit survives the sizing readback

vga_pci_map_bios() allocates the expansion-ROM resource with vga_pci_alloc_resource(dev, NULL, SYS_RES_MEMORY, &rid=PCIR_BIOS, 0, ~0, 1, RF_ACTIVE, -1) (sys/bus/pci/vga_pci.c:217). DF, unlike FreeBSD, never pre-reserves the ROM BAR in the device's resource list during enumeration (sys/bus/pci/pci.c:2720-2751 stop at BAR5), so the request falls into the lazy pci_alloc_map() (pci.c:4060), which performs the standard sizing dance:

pci_write_config(child, *rid, 0xffffffff, 4);   /* pci.c:3934 */
testval = pci_read_config(child, *rid, 4);      /* pci.c:3935 */

For a PCI expansion ROM BAR, bit 0 is the decode-enable bit and is RW, so a spec-conformant device (and QEMU's std VGA) reads back base_mask | 0x1 β€” empirically 0xffff0001 on the guest (see rombar_probe.sh and the DF-5467 serial log). pci_alloc_map() then runs

if (PCI_BAR_MEM(testval)) { ... } else {
    if (type != SYS_RES_IOPORT) { ...goto out; }   /* pci.c:3958-3965 */
}

with PCI_BAR_MEM(x) == ((x & PCIM_BAR_SPACE) == PCIM_BAR_MEM_SPACE) (pcireg.h:170-171) β€” the enable bit makes the ROM BAR look like an I/O BAR, the SYS_RES_MEMORY request is rejected, and vga_pci_map_bios() prints vga_pci_alloc_resource failed and returns NULL (vga_pci.c:220-223).

Consequences (functional, fails closed β€” no memory unsafety):

  • On any device whose ROM BAR readback preserves bit 0, the ROM path of vga_pci_map_bios() can never succeed. Non-boot-display VBIOS reads by drm drivers (sys/dev/drm/radeon/radeon_bios.c:86 and the amdgpu/i915 equivalents) always fail and must fall back to other BIOS sources; the D3-reset dance and the 1s/3s poll loops in vga_pci_map_bios() are unreachable dead code on such hardware.
  • The DF-5467 corruption chain is masked on exactly such hardware (the alloc fails before the map/unmap asymmetry can run); it is live on hardware whose ROM BAR readback masks bit 0 (and whenever the ROM BAR is pre-reserved, e.g. via resource hints).

Empirical proof on the guest: rombar_probe.sh β€” write 0xffffffff to reg 0x30 of pci0:0:2:0, read back 0xffff0001; restore. The unseeded df5467 KLD run shows vgapci0: vga_pci_alloc_resource failed (see ../DF-5467/serial-full.log lines around the first two run attempts).

Either pre-reserve the ROM BAR during enumeration like FreeBSD, or make the lazy classifier ignore the ROM BAR's enable bit before testing the space type, e.g. in pci_alloc_map():

--- a/sys/bus/pci/pci.c
+++ b/sys/bus/pci/pci.c
@@
    res = NULL;
    map = pci_read_config(child, *rid, 4);
    pci_write_config(child, *rid, 0xffffffff, 4);
    testval = pci_read_config(child, *rid, 4);
+   if (*rid == PCIR_BIOS || *rid == PCIR_BIOS_1)
+       testval &= ~0x1;    /* ROM BAR bit 0 is decode-enable */
    if (pci_maprange(testval) == 64)

(and the same mask where PCI_BAR_MEM(testval) is evaluated at pci.c:3948).

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not validated on a rebuilt kernel (impact none; the fix is a one-line pci.c classification change and the DF-5467 fix-validation cycle covers the same file family's build); readback probe is fix-independent evidence

README.md carries the proposed pci.c diff; run.log carries the readback proof
per-fix-DF-5468

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (probe: current ROM BAR febc0000 -> write 0xffffffff -> readback ffff0001 -> restored); ../DF-5467/serial-full.log lines 20-29 (two unseeded map attempts, each 'vgapci0: vga_pci_alloc_resource failed'); README.md code chain vga_pci.c:217 -> pci.c:4060 -> pci.c:3934-3966

PoC changes

seed sketch replaced by a root shell probe (pciconf write/read/restore of reg 0x30) - no build step needed; kernel-side alloc failure cited from the DF-5467 baseline serial log captured on the same kernel build

Verified recommended fix

Pre-reserve the ROM BAR (PCIR_BIOS) in the resource list during enumeration like FreeBSD, or mask the ROM decode-enable bit (testval &= ~0x1 for rid PCIR_BIOS/PCIR_BIOS_1) in pci_alloc_map() before the PCI_BAR_MEM classification - diff in README.md

Verdict

The ROM path of vga_pci_map_bios() is dead on spec-conformant devices. DF, unlike FreeBSD, never pre-reserves the expansion-ROM BAR in the device resource list during enumeration (pci.c:2720-2751 stop at BAR5), so vga_pci_map_bios()'s allocation (vga_pci.c:217) falls into the lazy pci_alloc_map() (pci.c:4060), whose sizing dance writes 0xffffffff to the ROM BAR and reads back base_mask|enable_bit (PCI ROM BAR bit 0 is RW; empirically 0xffff0001 on the QEMU std VGA, captured in run.log and ../DF-5467/serial-full.log). PCI_BAR_MEM(testval) is then false (pcireg.h:170-171 test bit 0), so the SYS_RES_MEMORY request is rejected as an ioport BAR (pci.c:3958-3965) and map_bios prints 'vga_pci_alloc_resource failed' and returns NULL (vga_pci.c:220-223). Consequence: non-boot-display VBIOS reads by drm (radeon_bios.c:86 and amdgpu/i915 equivalents) always fail on such hardware and the 1s/3s poll loops and D3-reset dance in map_bios are unreachable dead code. Fails closed - no memory unsafety - so impact 'none' (functional degradation only). Reproduced live: rombar_probe.sh readback + the unseeded map attempts in the DF-5467 baseline session.