# DF-4164 — VERDICT

**Status: inconclusive (unlocked-walk structure certain, race timing
hardware-gated).**
**Expected impact if hardware present: UAF read / list corruption from an
unprivileged-triggerable race → panic (DoS); controlled exploitation unlikely.**

## Reproduced?

No — guest has no radeon GPU (env.txt). Structure proven from source:

* Walker: `radeon_unmap_vram_bos` (radeon_pm.c:146-157) — no `gem.mutex`, no
  reference taken on `bo`; invoked from `radeon_pm_set_clocks` (:279) with
  `pm.mutex` + `ring_lock` + `mclk_lock` held — none of which serialize
  GEM-object creation/destruction.
* Writers, all under `gem.mutex`:
  * create: radeon_gem.c:89-91 (`list_add_tail`),
  * destroy: radeon_object.c:77-79 (`list_del_init` inside
    `radeon_ttm_bo_destroy`, immediately followed by `kfree(bo)` at :84),
  * force delete: radeon_object.c:441-443.
* Unprivileged reachability of both sides: GEM create/close via drm ioctls and
  fd close; PM-side triggers: ACPI power-profile events (:88), resume/fbcon
  (radeon_device.c:1816/:1929), fini_old. DPM-method machines are not exposed
  (change_power_state_locked does not call the unmap helper) — exposure is
  profile-method hw: pre-DPM asics or firmware-missing fallbacks
  (radeon_pm.c:1533-1542/1571-1582). PM_METHOD_DYNPM is dead on DFly (its only
  entry is the WIP'd sysfs block :354-786; `schedule_delayed_work` for the
  dynpm idle work is likewise fully WIP'd — the work can never start).

## Why `_safe` doesn't save it

`list_for_each_entry_safe` prefetches `n = bo->list.next`. If `n`'s object is
destroyed between the prefetch and the loop's next iteration, the iteration
dereferences freed memory (`bo = n; bo->tbo.mem.mem_type`). Concurrent
`list_add_tail` at the tail can equally strand/rotate the walk. Only a lock
(or per-object refs shared with the destroy path) closes it.

## Fix validation

fix.diff wraps the walk in `rdev->gem.mutex`. Lock-order analysis: every
`gem.mutex` critical section in the tree touches only list pointers
(verified: radeon_gem.c:89-91, 794-795; radeon_object.c:77-79, 441-443 —
none nest ttm/reservation locks), so holding it across
`ttm_bo_unmap_virtual()` cannot create an inversion with existing users.
`git apply --check` clean. No runtime validation possible on this guest.

## Adjacent negative results (this hunt)

* `radeon_pm_print_states` name array [5] is only indexed by legacy types
  0..4 (enum radeon.h:1206-1225; DPM uses the separate
  `radeon_dpm_print_power_state`); legacy combios/atombios parsers never emit
  the *_INTERNAL_* types → no OOB read of the static array.
* `radeon_set_power_state`'s `power_state[requested_*_index]` indices come
  from the fixed profile tables / dynpm enumeration — bounded.
* vblank get/put pairing in `radeon_pm_set_clocks` (:281-307) is symmetric
  (`req_vblank` only set on successful get).
* dynpm idle work handler (:1898-1957) is dead code on DFly (never scheduled
  — all `schedule_delayed_work` call sites are `#ifdef DUMBBELL_WIP`).

## 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.
