# DF-4413 — VERDICT

**status: not_reproduced (hardware absent) — finding certain by trace; not a false positive**

## Root cause (certain)

`radeon_vm_bo_set_addr()` records the mapping *before* it allocates the
backing page tables, and its page-table allocation loop has error returns
that leave the half-initialized mapping committed:

1. `sys/dev/drm/radeon/radeon_vm.c:519-526` — under `vm->status_lock`, sets
   `bo_va->it.start/it.last`, adds `bo_va` to `vm->cleared`, then
   `interval_tree_insert(&bo_va->it, &vm->va)` — **mapping committed**.
2. `radeon_vm.c:538` — `radeon_bo_unreserve(bo_va->bo)`.
3. `radeon_vm.c:541-575` — the PT allocation loop:
   * `radeon_vm.c:550-555` — `radeon_bo_create(...)` fails → `return r;`
   * `radeon_vm.c:557-561` — `radeon_vm_clear_bo()` fails → `radeon_bo_unref(&pt); return r;`
   Neither path removes the committed mapping → `bo_va` sits in
   `vm->va`/`vm->cleared` with `vm->page_tables[pt_idx].bo == NULL` for
   the un-allocated tail (or for every index if the first PT failed).
4. `radeon_gem_va_ioctl()` (radeon_gem.c:690-695) only calls
   `radeon_gem_va_update_vm()` `if (!r)` — the failed MAP is reported to
   userspace as an error while the kernel state keeps the mapping.

## Crash sinks (every subsequent update of that bo_va)

`radeon_vm_bo_update()` passes `bo_va->it.start, bo_va->it.last + 1` to
`radeon_vm_update_ptes()` (radeon_vm.c:1012-1014); the walk does:

```c
827:	uint64_t pt_idx = addr >> radeon_vm_block_size;
828:	struct radeon_bo *pt = vm->page_tables[pt_idx].bo;   /* NULL */
833:	radeon_sync_resv(rdev, &ib->sync, pt->tbo.resv, true); /* panic */
```
(The `pt_idx` itself is in-bounds — the `page_tables[]` array is sized
`num_pdes` in `radeon_vm_init` — the stored *value* is NULL.)

Reachable from three unprivileged paths after the failed MAP:

* **GEM_VA UNMAP of the same BO** — `radeon_gem.c:586` →
  `radeon_vm_clear_freed()` (radeon_vm.c:1060) → `radeon_vm_bo_update(NULL)`
  → `radeon_vm_update_ptes` → NULL deref. The "cleanup" unmap is itself
  the trigger (deterministic).
* **CS referencing the BO** — `radeon_cs.c:527` → same walk.
* **Eviction/invalidate** — `radeon_vm_bo_invalidate()` +
  `radeon_vm_clear_invalids()` (radeon_vm.c:1098) → same walk.

## Precondition realism

PT allocation failure requires TTM to be unable to provide one 4 KB VRAM
BO even after eviction: VRAM+GTT+system under pressure (unprivileged
GEM_CREATE spam), or `radeon_vm_clear_bo()`'s `radeon_ib_get()` failing
under SA-pool pressure / GPU reset. The error path exists precisely
because the authors expected these failures — but its state on return is
inconsistent, and the very next touch panics.

## Why not reproduced here

No radeon GPU on the audit guest (std VGA only; `/dev/dri` absent).
Trigger compiles and runs, exits at `open("/dev/dri/card0")` (run.log).
The chain above is unconditional source fact; only the memory-pressure
setup is guest-hardware dependent. Severity Medium (local kernel panic /
DoS from an unprivileged DRI client).

## Recommended fix

On failure inside the PT loop, roll the mapping back out of
`vm->va`/`vm->cleared` (restore `it.start/it.last = 0`) before returning.
See `fix.diff`.

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