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

Per-device runtime mutation of shared static radeon_asic dispatch tables (r420 'macs' hack, agp_disable gart swap, IGP clock-op NULL-out) β€” cross-device dispatch corruption in multi-GPU systems

Summary

The per-family dispatch tables are non-const statics shared by every device of that family, yet radeon_asic_init's r420 'handle macs' branch writes legacy PM accessors into r420_asic when rdev->bios==NULL, radeon_agp_disable overwrites gart ops in the shared table, and the IGP branch NULLs clock ops in place. The first card hitting the condition permanently rewrites dispatch for every other card of the same family probed later in the same boot (e.g. a BIOS-present R423 probed after a BIOS-less R420 loses atom clock programming; a working RS400 gets PCI-gart ops). Hardware-configuration trigger, type-correct wrong pointers, no memory-safety primitive - Low. Fix: per-device kmemdup of the template.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3994 Β· 4 files
FileTypeDescriptionSize
README.md β€” 3.4 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 796 B view raw

DF-3994 β€” Per-device runtime mutation of the shared static radeon_asic dispatch tables

Finding

The per-family dispatch tables in sys/dev/drm/radeon/radeon_asic.c are non-const static structs shared by every device of that family, yet three code paths write into them per device at runtime:

  1. r420 "macs" hack β€” radeon_asic_init(), radeon_asic.c:2358-2365: c rdev->asic = &r420_asic; /* handle macs */ if (rdev->bios == NULL) { rdev->asic->pm.get_engine_clock = &radeon_legacy_get_engine_clock; rdev->asic->pm.set_engine_clock = &radeon_legacy_set_engine_clock; rdev->asic->pm.get_memory_clock = &radeon_legacy_get_memory_clock; rdev->asic->pm.set_memory_clock = NULL; rdev->asic->display.set_backlight_level = &radeon_legacy_set_backlight_level; } r420_asic is selected for CHIP_R420 / CHIP_R423 / CHIP_RV410 (radeon_asic.c:2354-2357). The first BIOS-less card of that class permanently rewrites the table for every R420/R423/RV410 card probed afterwards in the same boot β€” including cards with a VBIOS, which then run legacy (pre-atom) PLL programming and lose set_memory_clock (NULL).

  2. radeon_agp_disable() β€” radeon_asic.c:167-175 rewrites rdev->asic->gart.{tlb_flush,get_page_entry,set_page} when AGP init fails. For an AGP-flagged RS400/RS480 (rs400_asic), the shared table's rs400 gart ops are replaced by r100_pci_* ops β€” a second, non-AGP RS400 card would then run PCI-gart programming on RS400 hardware.

  3. IGP clock-op NULL-out β€” radeon_asic_init(), radeon_asic.c:2696-2699: if (rdev->flags & RADEON_IS_IGP) { rdev->asic->pm.get_memory_clock = NULL; rdev->asic->pm.set_memory_clock = NULL; } β€” writes into the shared table (benign for the current family groupings, where each affected struct is used exclusively by IGPs, but the same footgun: any future mixed IGP/discrete sharing of a table gets silently corrupted).

Threat model / impact

Requires two same-family radeon GPUs in one machine where exactly one hits the mutating condition (BIOS-less R420-class + BIOS-present R420-class; or AGP-failed RS400 + working RS400). Probe order decides which card's needs overwrite the other's dispatch ops. Result: wrong PM/gart ops on the surviving card β€” legacy PLL writes instead of atom on an atom card, PCI-gart programming on rs400 hardware β€” i.e. incorrect MMIO sequences to the GPU / gart, link/system instability, potential GPU lockups. Not attacker-triggerable from userspace; it is a state-isolation defect (CWE-362/CWE-667) whose trigger is hardware configuration. Severity: Low.

(Upstream Linux carries the same pattern; the fix β€” copying the template into a per-device allocation or gating by device rather than mutating the template β€” applies equally there.)

Fix sketch

--- a/sys/dev/drm/radeon/radeon_asic.c
+++ b/sys/dev/drm/radeon/radeon_asic.c
@@ radeon_asic_init()
    case CHIP_R420:
    case CHIP_R423:
    case CHIP_RV410:
-       rdev->asic = &r420_asic;
+       rdev->asic = kmemdup(&r420_asic, sizeof(struct radeon_asic), GFP_KERNEL);
        /* handle macs */
        if (rdev->bios == NULL) {
            rdev->asic->pm.get_engine_clock = &radeon_legacy_get_engine_clock;

(per-device copy, freed in radeon_device_fini; same for the two other mutation sites, or convert them to device-local override pointers).

VERDICT.md
↓ download raw

DF-3994 β€” VERDICT

Status: inconclusive (source-certain behavior, hardware-gated practical impact). Low severity β€” configuration-dependent dispatch corruption, not user-triggerable.

Verified at source level

  • The tables are file-scope mutable statics: static struct radeon_asic r420_asic = {...} (sys/dev/drm/radeon/radeon_asic.c:498) etc., assigned by pointer at radeon_asic.c:2357 (rdev->asic = &r420_asic).
  • Mutation sites write through rdev->asic->… into the shared object: radeon_asic.c:2360-2364 (r420 macs), 167-175 (radeon_agp_disable), 2697-2698 (IGP NULL-out). No per-device copy exists anywhere between assignment and mutation (verified by reading radeon_asic_init() end to end and grepping all ->asic->… = writes across the driver).
  • Consumers of the affected ops: radeon_pm.c:805/837-style NULL-guards exist for optional ops, but the values dispatched (legacy vs atom clock accessors) are what get corrupted for the second card; gart op consumers call rdev->asic->gart.* unconditionally after init (e.g. radeon_gart.c via radeon_gart_tlb_flush wrapper).

Why not reproduced

Needs two same-family radeon cards with asymmetric BIOS/AGP conditions in one chassis; the audit guest has no radeon hardware at all (DF-3993 env.txt). No userspace trigger exists by construction β€” the bug fires at probe time.

Impact ceiling

Hardware-misprogramming on the co-tenant card (wrong PM accessor family / wrong gart ops) β†’ GPU/system instability. No kernel memory-safety consequence: the corrupted values are all valid function pointers of the correct prototype, so this is a correctness/isolation defect, not a code-execution primitive.

Fix

Per-device copy of the asic template (kmemdup) before any runtime override β€” see README.md fix sketch; keeps sys/ untouched per audit rules.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix is a per-device kmemdup of the dispatch template (README.md sketch); not testable on a guest with no radeon hardware.

findings/poc/DF-3994/README.md#fix-sketch
per-fix-DF-3994

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (mutation sites + consumers), VERDICT.md (why no memory-safety consequence, why untestable here)

PoC changes

n/a (analysis-only; no runnable trigger exists for a probe-time configuration bug)

Verified recommended fix

Copy the static asic template into a per-device allocation (kmemdup) in radeon_asic_init() before any runtime override, and free it in device fini

Verdict

Source-certain state-isolation defect: radeon_asic_init()/radeon_agp_disable() mutate the shared static per-family dispatch tables through rdev->asic (radeon_asic.c:2360-2364, 167-175, 2697-2698), so the first BIOS-less R420-class card (or AGP-disabled RS400) permanently rewrites PM/gart ops for every other card of the same family probed later in the same boot. Consequence is wrong (but type-correct) dispatch on the co-tenant card - legacy-vs-atom clock programming, r100_pci-vs-rs400 gart ops - i.e. hardware misprogramming/instability, not a memory-safety primitive. Not user-triggerable; requires a dual-same-family-GPU machine with asymmetric BIOS/AGP conditions, which the audit guest cannot provide (no radeon hardware). Low severity hardening/correctness finding.