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)
PoC verification
Evidence pack
findings/poc/DF-3994 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
-
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_asicis 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 loseset_memory_clock(NULL). -
radeon_agp_disable()β radeon_asic.c:167-175 rewritesrdev->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 byr100_pci_*ops β a second, non-AGP RS400 card would then run PCI-gart programming on RS400 hardware. -
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).
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 readingradeon_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 callrdev->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_testableFix 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
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.
No comments yet.