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

isavga_attach() discards both bus_alloc_resource() returns and never unwinds the legacy VGA window claims on attach failure β€” permanent orphaned rman claims (I/O 0x3c0-0x3df, MEM 0xa0000-0xbffff) on VGA-less BIOS boots, plus leaked cdev under FB_INSTALL_CDEV

Summary

Both legacy-window claims are activated with discarded returns and the attach returns early on vga_attach_unit failure without releasing anything; no detach/shutdown exists at all, so failed-attach orphans the claims permanently. The failure branch is the EXPECTED outcome on VGA-less BIOS hardware (vga_probe returns 0 even after verify_adapter failed) - zombie claims block later kldloaded display drivers with EBUSY (narrow resource-starvation DoS); with FB_INSTALL_CDEV the cdev leaks too. Live baseline captured (devinfo shows both claims); failure path proven by line trace (rig's Bochs VGA always passes). DF-5467 ROM-teardown class ABSENT (nothing mapped, no detach); DF-5469 rid-aliasing ABSENT (fixed rid 0). check+release+destroy_dev fix proposed.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5590 Β· 10 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 4.7 KB ↓ raw
introspect.sh β€” 722 B view raw
build.sh β€” 105 B view raw
run.sh β€” 89 B view raw
run.log β€” 731 B view raw
env.txt β€” 1.2 KB view raw
fix.diff β€” 1.3 KB view raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.9 KB view raw

DF-5590 β€” isavga_attach() never unwinds the legacy VGA window claims on failure

What this is

Evidence pack for the pass-2 finding on sys/bus/isa/vga_isa.c:

isavga_attach() activates the two legacy VGA resource windows (SYS_RES_IOPORT 0x3c0-0x3df, SYS_RES_MEMORY 0xa0000-0xbffff) at sys/bus/isa/vga_isa.c:144-148, discards both bus_alloc_resource() return values, and then has no error unwinding when vga_attach_unit() fails (vga_isa.c:150-152) or fb_attach() fails (vga_isa.c:158-160). There is no detach method at all (vga_isa.c:66-74), so on a failed attach the claims are permanent.

The failure path is not exotic: isavga_probe() succeeds even when no VGA hardware is present, because vga_probe() returns 0 unconditionally after probe_adapters() fails verification (sys/dev/video/fb/vga.c:838-852, verify_adapter() write/read test at vga.c:3951-... / probe_adapters at vga.c:616-...). On any legacy-BIOS boot without VGA decode (server with onboard VGA disabled, -vga none style VM), the expected outcome is: probe claims success -> attach claims both windows in the ISA rman -> vga_init() returns ENXIO because V_ADP_PROBED was never set (vga.c:855-859) -> attach fails -> claims leak forever on a zombie device. With options FB_INSTALL_CDEV the make_dev()'d cdev also leaks (vga_isa.c:156-160).

Reproduce

Nothing to compile β€” this is runtime introspection of the live driver:

./run.sh          # from the repo root (uses dfbsd-qemu/vm.sh)

run.sh collects: dmesg attachment lines, the rman window claims (devinfo -rv), and proof the mechanism exists on the running kernel (vga0 attached on isa1 with exactly port 0x3c0-0x3df / iomem 0xa0000-0xbffff).

The leak itself requires attach to fail, i.e. a BIOS boot with no VGA decode (QEMU -vga none, or onboard VGA disabled in BIOS). The audit rig's Bochs VGA always passes verify_adapter(), so the failure branch cannot be entered on this guest β€” see VERDICT.md for the line-by-line path trace.

Expected output

Successful-attach baseline (what the rig shows):

vga0: <Generic ISA VGA> at port 0x3c0-0x3df iomem 0xa0000-0xbffff on isa1

On a VGA-less BIOS machine the same GENERIC kernel would print nothing for vga0 (attach fails) yet hold both rman claims β€” that asymmetry is the bug; fix.diff unwinds it.

VERDICT.md
↓ download raw

DF-5590 VERDICT β€” inconclusive at runtime (mechanism code-certain)

Finding: isavga_attach() claims the legacy VGA I/O and memory windows and never unwinds them when vga_attach_unit() / fb_attach() fail; the returned struct resource *s are discarded and the driver has no detach method, so failed-attach leaves permanent rman claims (and, with FB_INSTALL_CDEV, a leaked cdev).

Runtime status: inconclusive β€” the defect is certain from source, but the failure branch could not be entered on this rig because its Bochs VGA always passes verify_adapter(). This is a hardware-absence trigger, not a software precondition the guest can express (QEMU args are fixed by dfbsd-qemu/vm.sh; no loader(8) knob makes verify_adapter() fail while still creating the vga0 isa child).

Line-by-line path trace (why the mechanism is certain)

  1. device vga0 at isa? β€” sys/config/X86_64_GENERIC:155 creates the isa child; boot enumerates it (dmesg: vga0 ... on isa1).
  2. isavga_probe() sys/bus/isa/vga_isa.c:105-130: - PnP guard ok (:112). - vga_probe_unit() -> vga_probe() sys/dev/video/fb/vga.c:838-853: returns 0 even when probe_adapters() failed (no V_ADP_PROBED), because the return of probe_adapters() at vga.c:846 is ignored; *adpp = &biosadapter is handed out regardless (:850). - Probe therefore "succeeds" on VGA-less hardware and programs the static windows into the resource list: bus_set_resource() IOPORT IO_VGA(0x3c0)/32, MEMORY 0xa0000/0x20000 (vga_isa.c:118-121, values from the static biosadapter at vga.c:175-179).
  3. isavga_attach() vga_isa.c:132-172: - bus_alloc_resource() IOPORT :144-145 and MEMORY :147-148 with RF_ACTIVE|RF_SHAREABLE β€” return values discarded; with start=0/end=~0/count=0 these allocate exactly the ranges set in probe (resource_list_alloc() isdefault path, sys/kern/subr_bus.c:2693-2698). - vga_attach_unit() :150 -> vga_init() vga.c:855-859 returns ENXIO when !probe_done(adp) β€” the normal result on VGA-less hardware. - if (error) return error; :151-152 β€” the two activated rman claims are not released. The newbus child remains in the tree with its resource-list entries holding live struct resource *s (rle->res set by resource_list_alloc, subr_bus.c:2705-2711). - With FB_INSTALL_CDEV: make_dev()+reference_dev() at :156-157, then fb_attach() failure at :158-160 also returns without destroy_dev() β€” leaked cdev (LINT-only; GENERIC has no FB_INSTALL_CDEV, sys/conf/options:404).
  4. No unwind ever happens later: isavga_methods vga_isa.c:66-74 has no device_detach and no device_shutdown β€” nothing can reclaim the claims short of a reboot.

Observed live (baseline, successful attach β€” the healthy twin of the bug)

$ dmesg | grep vga0
vga0: <Generic ISA VGA> at port 0x3c0-0x3df iomem 0xa0000-0xbffff on isa1

$ devinfo -rv | grep -A5 vga0     # works as UNPRIVILEGED user
vga0
    I/O ports:
        0x3c0-0x3df
    I/O memory addresses:
        0xa0000-0xbffff

This proves the claim mechanism (probe bus_set_resource -> attach bus_alloc_resource -> rman ranges published in devinfo) is exactly as traced; on a VGA-less BIOS boot the same two ranges are claimed and then orphaned when attach fails two lines later.

Impact

  • No memory corruption. The orphaned claims block any later legitimate claimant of the legacy VGA windows (an isa-attached display driver loaded via kldload would get EBUSY): local resource-starvation DoS with a narrow ceiling, plus a leaked cdev on LINT kernels.
  • Trigger: BIOS boot with no VGA decode (or any vga_attach_unit() failure). No unprivileged-user action is involved in triggering it; the finding is correctness/robustness with a DoS tail.

Why the fix is what it is

Check both bus_alloc_resource() returns, release them when vga_attach_unit() fails, and destroy_dev() when fb_attach() fails. fix.diff in this pack implements exactly that against the read-only sys/ tree. Fix validation was not performed: no memory corruption reproduced (contract mandates kernel-rebuild validation only then), and the fixed branch is untriggerable on this rig for the same hardware reason as above β€” hence fix_status: not_testable.

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored against the read-only sys/ tree (patch -p1 dry-run OK) implementing the unwind; kernel-rebuild validation not performed: no memory corruption reproduced (contract mandates rebuild validation only then) and the fixed failure branch is untriggerable on this rig for the same hardware-absence reason as the baseline (rig's Bochs VGA always verifies; vm.sh QEMU args are fixed, no -vga none control).

findings/poc/DF-5590/fix.diff (unified diff vs sys/bus/isa/vga_isa.c, dry-run applied clean)
↓ fix.diffper-fix-DF-5590

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5590/run.log (dmesg 'vga0 ... at port 0x3c0-0x3df iomem 0xa0000-0xbffff on isa1' + devinfo -rv claims); VERDICT.md (line-by-line trace: probe success-on-absent-hardware at vga.c:846-852, resource_list_alloc isdefault path subr_bus.c:2693-2698, vga_init ENXIO at vga.c:855-859, no unwind at vga_isa.c:151-152/159-160, no detach at vga_isa.c:66-74); env.txt (GENERIC, no FB_INSTALL_CDEV, no /dev/vga0)

PoC changes

No seeded PoC existed; authored an introspection harness (introspect.sh/run.sh) capturing the live claim mechanism as the baseline; the failure branch is documented by source trace in VERDICT.md since it needs VGA-less BIOS hardware.

Verified recommended fix

Check both bus_alloc_resource() returns and bus_release_resource() them on vga_attach_unit() failure; destroy_dev() on fb_attach() failure (fix.diff).

Verdict

Code-certain defect, runtime-inconclusive on this rig: isavga_attach() (sys/bus/isa/vga_isa.c:144-148) activates the legacy VGA windows (I/O 0x3c0-0x3df, MEM 0xa0000-0xbffff) discarding both bus_alloc_resource() returns, and returns early at :150-152 (vga_attach_unit failure β€” the EXPECTED outcome on BIOS hardware without VGA decode, since vga_probe() returns 0 even when verify_adapter() failed, vga.c:838-853) or :158-160 (fb_attach failure, leaking the make_dev'd cdev) without releasing them; the method table at vga_isa.c:66-74 has no detach/shutdown, so the claims are permanent. Live baseline on the QEMU guest (vga0 attached on isa1, devinfo -rv shows both claims, run.log) proves the claim mechanism exactly as traced, but the failure branch cannot be entered because the rig's Bochs VGA always passes verify_adapter() and QEMU args are fixed by vm.sh β€” a hardware-absence trigger, not a guest-expressible software precondition. No memory corruption; ceiling is permanent legacy-window rman starvation (blocks later kldloaded claimants with EBUSY) plus a LINT-only cdev leak.