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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4579 Β· 7 files
FileTypeDescriptionSize
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)

  1. radeon_driver_attach (radeon_drv.c:812-839) is the live newbus attach entry (DRIVER_MODULE_ORDERED at :879-880). It calls radeon_pci_probe (:834).
  2. radeon_pci_probe (radeon_drv.c:382-434) calls drm_get_pci_dev(pdev, ent, &kms_driver) (:433) β€” the only live call site of drm_get_pci_dev in the tree (the other, drm_pci.c:404, is inside the entirely #if 0'd drm_legacy_pci_init, which also early-returns -EINVAL for non-LEGACY drivers).
  3. drm_get_pci_dev (drm_pci.c:309-357) does dev = drm_dev_alloc(...) (:318) then ret = drm_dev_register(dev, ent->driver_data) (:338-340); on failure it jumps to err_agp β€” and the entire error block is #if 0'd out (drm_pci.c:343-357): upstream Linux has err_free: drm_dev_unref(dev); there, DragonFly deleted it. The function returns ret with dev leaked.
  4. drm_dev_register (drm_drv.c:877-924) fails whenever: - drm_minor_register fails (drm_drv.c:884-890), or - driver->load fails: radeon_driver_load_kms returns 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). Its err_minors path (drm_drv.c:915-918) only unregisters the minors β€” it does not free dev and does not call driver->unload.
  5. radeon_driver_load_kms (radeon_kms.c:95-173) cleans up its own partial state by calling radeon_driver_unload_kms(dev) on error (:170) β€” so the leak boundary is exactly one struct drm_device (plus its embedded mutexes/idr state) per failed attach.
  6. Back in radeon_driver_attach, on error != 0 the caller correctly frees the pci_dev mirror via drm_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.md
↓ download raw

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):
  • :318 dev = drm_dev_alloc(driver, &pdev->dev);
  • :338-340 ret = drm_dev_register(dev, ent->driver_data); if (ret) goto err_agp;
  • :343-357 the whole err_agp block β€” including upstream's err_free: drm_dev_unref(dev); β€” is #if 0'd out. On any register failure the function returns with dev allocated and never freed. Upstream Linux drm_pci.c has a live drm_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 live drm_get_pci_dev caller in the tree β€” drm_pci.c:404 sits in the fully-#if 0 drm_legacy_pci_init, which also WARNs+returns -EINVAL for non-DRIVER_LEGACY drivers).
  • Failure frequency: drm_dev_register (drm_drv.c:877-924) fails whenever radeon_minor_register fails, or driver->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_kms self-cleans via radeon_driver_unload_kms(dev) (radeon_kms.c:170); drm_dev_register's err_minors (drm_drv.c:915-918) unregisters minors only. So exactly one struct 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 the pci_dev mirror on every error return of radeon_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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4579

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.