radeon: every failed PCI attach leaks one drm_device β DragonFly's drm_get_pci_dev has its entire error unwind #if 0'd out (upstream err_free: drm_dev_unref deleted); radeon_pci_probe is the only live caller
Summary
On any register/load failure the err_agp block of drm_get_pci_dev is entirely #if 0'd out - the port deleted upstream's drm_dev_unref(dev) - so the drm_device leaks; radeon's load self-cleans and the caller's pdev mirror is freed, so exactly one drm_device leaks per failed attach, repeatable per kldload/boot with failing ATI hardware. Restore-the-unwind fix (drm_pci.c, git-apply-verified). Also noted cross-file: drm_init_pdev unchecked kzallocs + drm_dev_register publishes minors before driver->load (upstream-inherited window).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4579 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| env.txt | β | 779 B | view raw | |
| run.log | β | 518 B | view raw | |
| fix.diff | β | 533 B | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.4 KB | view raw |
DF-4579 β radeon (and DF drm core): every failed radeon PCI attach leaks the drm_device β drm_get_pci_dev's error unwind is #if 0'd out
File: sys/dev/drm/radeon/radeon_drv.c (884 LOC) β pass 2 (GLM 5.3 re-audit).
Build/run/expected
Runtime reproduction requires ATI Radeon hardware: the leak fires on the PCI attach
path (radeon_driver_attach β radeon_pci_probe β drm_get_pci_dev) whenever
radeon_driver_load_kms fails fatally ("Fatal error during GPU init") or minor
registration fails. The audit guest has no ATI VGA device (virtio-gpu only, see
env.txt), so the defect is verified by source trace against the live code path;
there is no runnable trigger on this system.
# what would trigger it on ATI hardware (or forced):
kldload radeon # or boot with an ATI card whose init fails
dmesg | grep "Fatal error during GPU init" # radeon_kms.c:137
# each such failed attach leaks one struct drm_device (drm_dev_alloc) forever
The chain (all path:line verified)
radeon_driver_attach(radeon_drv.c:812-839) is the live newbus attach entry (DRIVER_MODULE_ORDEREDat :879-880). It callsradeon_pci_probe(:834).radeon_pci_probe(radeon_drv.c:382-434) callsdrm_get_pci_dev(pdev, ent, &kms_driver)(:433) β the only live call site ofdrm_get_pci_devin the tree (the other, drm_pci.c:404, is inside the entirely#if 0'ddrm_legacy_pci_init, which also early-returns-EINVALfor non-LEGACY drivers).drm_get_pci_dev(drm_pci.c:309-357) doesdev = drm_dev_alloc(...)(:318) thenret = drm_dev_register(dev, ent->driver_data)(:338-340); on failure it jumps toerr_agpβ and the entire error block is#if 0'd out (drm_pci.c:343-357): upstream Linux haserr_free: drm_dev_unref(dev);there, DragonFly deleted it. The function returnsretwithdevleaked.drm_dev_register(drm_drv.c:877-924) fails whenever: -drm_minor_registerfails (drm_drv.c:884-890), or -driver->loadfails:radeon_driver_load_kmsreturns error after "Fatal error during GPU init" (radeon_kms.c:131-138; e.g. failed VRAM/iomap/ firmware/TTM init β routine with flaky or unsupported ATI hardware). Itserr_minorspath (drm_drv.c:915-918) only unregisters the minors β it does not freedevand does not calldriver->unload.radeon_driver_load_kms(radeon_kms.c:95-173) cleans up its own partial state by callingradeon_driver_unload_kms(dev)on error (:170) β so the leak boundary is exactly onestruct drm_device(plus its embedded mutexes/idr state) per failed attach.- Back in
radeon_driver_attach, onerror != 0the caller correctly frees thepci_devmirror viadrm_fini_pdev(&pdev)(radeon_drv.c:836) β the caller's part of the unwind is right; the core's is not.
Impact
Kernel heap leak (one drm_device allocation per failed attach). Attach happens at
boot on ATI hardware or via kldload radeon (root), and can be re-attempted after
each failed probe β a repeated kldload/probe cycle with failing hardware leaks
unboundedly. Not reachable by an unprivileged user. Severity Low.
Why the guest run cannot reproduce it
pciconf -l: only vgapci0@pci0:0:2:0 card=0x11001af4 chip=0x11111234 (virtio-gpu,
Red Hat vendor 0x1af4) β no ATI 0x1002 device, /dev/dri absent, radeon.ko never
loads. QEMU cannot emulate radeon-compatible VGA. Source verification is therefore
the deliverable (VERDICT.md).
Fix
fix.diff restores the upstream error unwind in drm_get_pci_dev
(drm_dev_unref(dev) on the register-failure path; drm_dev_unref exists at
drm_drv.c:789). Authored against the read-only sys/ tree; not applied.
VERDICT DF-4579 β failed radeon attach leaks drm_device (DF drm_pci.c error unwind stubbed out)
Status: not_reproduced on this guest (no ATI hardware β radeon cannot attach at all). Severity Low, confidence certain (source-verified DF divergence from upstream).
Reproduced?
No β and provably not for lack of trying: the trigger requires an ATI Radeon PCI
device (attach-time load failure). The guest's only VGA is virtio-gpu
(vgapci0@pci0:0:2:0 card=0x11001af4 chip=0x11111234, env.txt), /dev/dri is
absent, and no radeon KLD loads. QEMU cannot present radeon-compatible hardware.
Following the DF-4564 precedent (radeon HW-absent verdicts), this is recorded as
not_reproduced with the source trace as the verification deliverable.
The defect is certain from source
drm_get_pci_dev(drm_pci.c:309-357)::318dev = drm_dev_alloc(driver, &pdev->dev);:338-340ret = drm_dev_register(dev, ent->driver_data); if (ret) goto err_agp;:343-357the wholeerr_agpblock β including upstream'serr_free: drm_dev_unref(dev);β is#if 0'd out. On any register failure the function returns withdevallocated and never freed. Upstream Linux drm_pci.c has a livedrm_dev_unref(dev)on this path; DragonFly's port stubbed it.- Reachability:
radeon_driver_attach(radeon_drv.c:812-839) βradeon_pci_probe(radeon_drv.c:433, the only livedrm_get_pci_devcaller in the tree β drm_pci.c:404 sits in the fully-#if 0drm_legacy_pci_init, which also WARNs+returns-EINVALfor non-DRIVER_LEGACY drivers). - Failure frequency:
drm_dev_register(drm_drv.c:877-924) fails wheneverradeon_minor_registerfails, ordriver->load=radeon_driver_load_kms(radeon_kms.c:95-173) returns the fatal-init error ("Fatal error during GPU init", radeon_kms.c:137). Load failures are routine with marginal/unsupported ATI hardware, bad firmware, or failed TTM init. - Leak boundary:
radeon_driver_load_kmsself-cleans viaradeon_driver_unload_kms(dev)(radeon_kms.c:170);drm_dev_register'serr_minors(drm_drv.c:915-918) unregisters minors only. So exactly onestruct drm_device(drm_dev_alloc: drm_drv.c:717-735) leaks per failed attach. - The radeon-side caller does its part correctly:
drm_fini_pdev(&pdev)(radeon_drv.c:836) frees thepci_devmirror on every error return ofradeon_pci_probe, whether DF-positive (ENXIO, :391/:428/:804) or Linux-negative (from drm_get_pci_dev) β both are non-zero and handled.
Impact ceiling
Kernel memory exhaustion over repeated failed attaches (boot-time on broken ATI
hardware, or root re-running kldload radeon after each failure β the probe can be
re-attempted indefinitely). No unprivileged trigger; no info leak; no corruption.
Severity Low (CWE-401).
Exploit chain
None (leak only; requires root or already-broken hardware at boot).
Fix validation
fix.diff restores the upstream unwind (drm_dev_unref(dev) β the function exists
at drm_drv.c:789 β moved out of the #if 0 block). Cannot be validated on this
guest (no ATI hardware β neither baseline nor patched behavior is observable);
fix_status: not_testable. Cross-check on real hardware would be:
baseline β repeated kldload with forced load failure leaks (vmstat -m / kldunload
without memory returning); patched β no leak.
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_testablefix.diff authored against read-only sys/ (restores upstream err_free: drm_dev_unref); cannot be runtime-validated on this guest (no ATI hardware β neither baseline nor patched behavior observable)
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full path:line trace and leak-boundary analysis); env.txt + run.log (guest has no 0x1002 device); fix.diff (restores upstream drm_dev_unref on the error path; drm_dev_unref exists at drm_drv.c:789)
PoC changes
not applicable β no trigger source; the PoC would be 'kldload radeon on failing ATI hardware', impossible on this guest
Verified recommended fix
restore the drm_get_pci_dev error unwind: move drm_dev_unref(dev) out of the #if 0 block so failed drm_dev_register frees the drm_device (fix.diff)
Verdict
Source-certain kernel memory leak, unreachable on this guest for lack of ATI hardware (only virtio-gpu present; /dev/dri absent; radeon.ko never attaches β QEMU cannot emulate radeon VGA; same HW-absent situation as DF-4564). The live chain: radeon_driver_attach (radeon_drv.c:834, the only live drm_get_pci_dev caller besides the fully-#if 0 drm_legacy_pci_init) -> radeon_pci_probe (radeon_drv.c:433) -> drm_get_pci_dev (drm_pci.c:318 drm_dev_alloc, :338 drm_dev_register) -> on register/load failure ('Fatal error during GPU init', radeon_kms.c:137) the err_agp block at drm_pci.c:343-357 is entirely #if 0'd out β upstream's err_free: drm_dev_unref(dev) was deleted in the DragonFly port β so dev leaks. drm_dev_register's err_minors (drm_drv.c:915-918) only unregisters minors; radeon_driver_load_kms self-cleans via radeon_driver_unload_kms (radeon_kms.c:170); the caller's drm_fini_pdev (radeon_drv.c:836) is correct. Exactly one struct drm_device per failed attach; re-attemptable per kldload/boot with failing hardware. Root/hardware-triggered only => Low.
No comments yet.