# DF-3666 VERDICT — undersized RLC save/restore BO in `sumo_rlc_init()`

## Reproduced?

**The index arithmetic is fully proven (deterministic, constant inputs); the write
itself executes in kernel context only on PALM/SUMO/SUMO2 hardware, which the audit
guest does not have** (virtio-only; see `env.txt`). Machine verdict: `inconclusive`
(hardware-bound), source-level certainty certain.

## The proof

- `evergreen.c:123-206`: `sumo_rlc_save_restore_register_list[]` has **81** entries
  (verified by count).
- `evergreen_startup()` (`evergreen.c:5016-5021`) sets `rdev->rlc.reg_list =` that
  array and `reg_list_size = 81` for every `RADEON_IS_IGP` evergreen-family part,
  then calls `sumo_rlc_init()`.
- `sumo_rlc_init()` (`evergreen.c:4145-4146`): `dws = 81`.
- Allocation (`evergreen.c:4155`): `radeon_bo_create(rdev, dws * 4 = 324 bytes, ...)`.
- Write loop (`evergreen.c:4197-4204`): each iteration packs 2 source dwords into
  `dst_ptr[j]`, `j = ((i-1)*3)/2` → writes at indices 0, 3, 6, ..., 120; epilogue
  (`evergreen.c:4205-4206`) writes the end marker at `j = (82*3)/2 = 123`.
  **Highest index 123 → 496 bytes touched vs 324 allocated → 43 dwords (172 bytes)
  past the BO's declared size.**
- Simulation replaying the exact loop (`df3666.c`) prints:
  `OOB write past BO size: 43 dwords = 172 bytes`,
  `RESULT: BUG REPRODUCED - writes need 496 bytes but the BO is 324 bytes`
  (see `run.log`; deterministic across runs).

## Why this is Low (containment analysis)

- TTM rounds the backing store to whole pages: `bo->num_pages =
  (324 + 4095) >> 12 = 1`, and radeon's GTT/VRAM managers allocate page-granular
  nodes, so byte offsets 324..495 land in the same page TTM already owns for this
  object. No adjacent TTM object, no host kernel memory is touched.
- The values written are compile-time constants (packed register addresses) and the
  RLC-save spaces the GPU fills later — nothing attacker-influenced, nothing
  secret-leaking.
- Consequence today: none observable (which is why the driver "works"). Consequence
  tomorrow: any sub-page VRAM allocator, or reuse of this packing loop with a
  different `reg_list` (it is shared: `ni.c:2204` passes the ARUBA
  `tn_rlc_save_restore_register_list` through the same loop — cross-file lead), or
  TTM slack reuse turns this into real VRAM corruption of a neighboring buffer
  object.

## Fix

`fix.diff` sizes the save/restore BO for the packed format (and keeps the linear
SI+ path as-is):

```diff
-		r = radeon_bo_create(rdev, dws * 4, PAGE_SIZE, true,
+		/* pre-SI packs 2 regs into 3 dwords, plus the end marker */
+		u32 sr_dws = (rdev->family >= CHIP_TAHITI) ? dws :
+				(((dws + 1) / 2) * 3 + 1);
+		r = radeon_bo_create(rdev, sr_dws * 4, PAGE_SIZE,
+				     true, RADEON_GEM_DOMAIN_VRAM, 0,
 				     NULL, NULL, &rdev->rlc.save_restore_obj);
```

(Upstream Linux fixed the same undersizing with the equivalent size expression.)

## Fix validation

Not kernel-testable on this guest (no SUMO/IGP radeon device; the code runs during
`evergreen_startup()` with `RADEON_IS_IGP`). The sizing expression is validated
arithmetically by `df3666.c`: for dws=81 the new allocation is
`((82)/2)*3+1 = 124` dwords = 496 bytes ≥ highest written index 123. fix_status =
`not_testable`.

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