# 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
```diff
--- 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).
