# DF-4916 — VERDICT

**Status: not_reproduced (hardware-gated — no AMD GPU / SR-IOV VF in the audit
guest). The bounds defect itself is certain from source; the trigger data is
host/PF-supplied, so guest-side reproduction requires SR-IOV hardware.**

## The chain, with the numbers

1. The size of the firmware VRAM reservation is taken verbatim from the
   vBIOS (in an SR-IOV guest the vBIOS is host-supplied):
   `amdgpu_atombios.c:1803-1807` and `amdgpu_atomfirmware.c:91-95` set
   `adev->fw_vram_usage.size = used_by_firmware_in_kb << 10`. The only
   constraint anywhere is `size > 0 && size <= visible_vram_size`
   (`amdgpu_ttm.c:1707-1708`). **No minimum size is enforced.**
2. `amdgpu_ttm_fw_reserve_vram_init()` (`amdgpu_ttm.c:1742-1743`) kmap's
   exactly `fw_vram_usage.size` bytes into `fw_vram_usage.va`.
3. `amdgpu_virt_init_data_exchange()` (`amdgpu_virt.c:392-443`) — called at
   `amdgpu_device.c:1695` (end of device init on VF) and
   `amdgpu_device.c:3345` (post-FLR re-init) — hardcodes
   `p_pf2vf = fw_vram_usage.va + AMDGIM_DATAEXCHANGE_OFFSET` with
   `AMDGIM_DATAEXCHANGE_OFFSET = 64 * 1024` (`amdgpu_virt.h:74`), and
   **immediately dereferences it** (`header.size`, `header.version`,
   version-dispatched `checksum` / `feature_flags` reads via
   `AMDGPU_FW_VRAM_PF2VF_READ`, `amdgpu_virt.h:225-235`) with **no check
   that the reservation is even 64 KB deep**. With a small-but-nonzero
   reservation every one of these reads is out of bounds of the kmap.
4. `pf2vf_size` (u32 read from VRAM, fully host-written) is gated only by
   `0 < pf2vf_size < 4096` (`amdgpu_virt.c:411`) and a checksum whose key is
   host-supplied (AI: mailbox 2, `mxgpu_ai.c:176-180`; VI: `checksum_key`
   stays 0 — never initialized by mxgpu_vi). The gate therefore binds
   nothing against the actual reservation size.
5. On "checksum ok": `p_vf2pf = p_pf2vf + pf2vf_size`
   (`amdgpu_virt.c:416-418`) and `memset(p_vf2pf, 0,
   sizeof(amdgim_vf2pf_info))` (`amdgpu_virt.c:419-420`).
   `sizeof(amdgim_vf2pf_info)` = 1024 (v2 struct is padded to 256 u32s by
   `AMDGIM_GET_STRUCTURE_RESERVED_SIZE(256, 64, 0, 14, 0)`,
   `amdgpu_virt.h:209`).
   Worst case the memset spans `[va+65536+1, va+65536+4095+1024)` — i.e. up
   to ~69 KB into a region the vBIOS may have declared as small as 1 KB.

So the guest kernel performs an OOB **write** (zeroes, 1 KB, host-chosen
offset in (64 KB, 68 KB)) and OOB **reads** on the kmap'ed VRAM BO, purely on
host-fed data. Behavior on x86-64 with an ioremap'ed TTM BO: access beyond
the mapping either hits adjacent device memory (silent corruption) or an
unmapped page (kernel page-fault panic). Guest userspace cannot reach this
path (device-init time, root/host-controlled inputs).

This is an inherited upstream defect (Linux v4.19 has the identical code;
verified against torvalds/linux v4.19 `amdgpu_virt.c` lines 396-436), not a
port-introduced one — but it is a real robustness hole in the guest-kernel
trust boundary the file exists to implement.

## Why not reproduced on the audit guest

No AMD PCI device exists in the QEMU guest (`env.txt`); `amdgpu.ko` never
binds; `fw_vram_usage.va` stays NULL and `amdgpu_virt_init_data_exchange`
returns at line 402. Hardware-gated → not_reproduced on this guest, impact
none *here*; the bound-math defect is certain.

## Exploit chain

None from guest userspace. From a malicious/compromised host position: guest
panic (init-time DoS) or 1 KB zero-corruption at a ~4 KB-wide host-chosen
offset past a known kmap. No guest-privileged code-exec primitive identified.

## Recommended fix

Validate the reservation actually contains the exchange area before any
dereference, e.g.:

```diff
--- a/sys/dev/drm/amd/amdgpu/amdgpu_virt.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_virt.c
@@ -399,7 +399,14 @@ void amdgpu_virt_init_data_exchange(struct amdgpu_device *adev)
 	adev->virt.fw_reserve.p_vf2pf = NULL;
 
-	if (adev->fw_vram_usage.va != NULL) {
+	if (adev->fw_vram_usage.va != NULL &&
+	    adev->fw_vram_usage.size >=
+		AMDGIM_DATAEXCHANGE_OFFSET + sizeof(amdgim_vf2pf_info)) {
 		adev->virt.fw_reserve.p_pf2vf =
 			(struct amdgim_pf2vf_info_header *)(
 			adev->fw_vram_usage.va + AMDGIM_DATAEXCHANGE_OFFSET);
```
plus, inside the `pf2vf_size` gate:

```diff
-		if (pf2vf_size > 0 && pf2vf_size < 4096) {
+		if (pf2vf_size >= sizeof(struct amdgim_pf2vf_info_v2) &&
+		    pf2vf_size + sizeof(amdgim_vf2pf_info) <=
+			adev->fw_vram_usage.size - AMDGIM_DATAEXCHANGE_OFFSET) {
```

(Upstream later grew equivalent guards; this mirrors them.)

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