# DF-4238 VERDICT — sdma_v3_0 firmware sub-header OOB read (DF-1451/1553/1598 family, v3 variant)

**Status: not_reproduced (hardware-gated: guest has no AMD GPU; parsing
primitive proven by a guest-run harness).** Confidence: certain (code).
Impact: heap OOB read on root-installed firmware → Low.

## 1. Root cause (path:line)

* amdgpu_ucode.c:251-260 — `amdgpu_ucode_validate()` checks ONLY
  `fw->datasize == le32_to_cpu(hdr->size_bytes)`. No minimum size, no
  header_size/ucode_size/offset cross-validation (root finding DF-1946).
* sdma_v3_0.c:309-317 — after `request_firmware` + the equality-only
  validate, the code casts `fw->data` to
  `struct sdma_firmware_header_v1_0` (48 bytes; amdgpu_ucode.h:120-126) and
  reads:
  - `hdr->header.ucode_version` — common header offset 16
    (sdma_v3_0.c:316),
  - `hdr->ucode_feature_version` — offset 32 (sdma_v3_0.c:317).
* sdma_v3_0.c:321-326 — `header->ucode_size_bytes` (offset 24) is folded
  into `adev->firmware.fw_size` with no bound vs `datasize`; the payload is
  later memcpy'd from `fw->data + ucode_array_offset_bytes` for
  `ucode_size_bytes` bytes into the firmware BO (amdgpu_ucode.c:347-351 —
  the DF-1947 sink; on VI the load type is AMDGPU_FW_LOAD_SMU,
  amdgpu_ucode.c:291-300, so the BO path is the live one for Tonga et al.).

A firmware blob of `datasize = 4` with `size_bytes = 4` passes validation;
reads at +16/+24/+32 are heap OOB. The reads at :316/:317 feed dmesg
(`fw_version`/`feature_version` influence `burst_nop` at :318-319) — an
OOB-read → dmesg leak channel; `fw_size` at :325-326 is inflated by the OOB
value read at +24.

## 2. Harness verification (guest)

`fwcheck.c` replicates the sequence against a 4-byte blob placed at the end
of a mapped page with a PROT_NONE guard page after it (stand-in for the
exact-size request_firmware allocation). Guest run (DragonFly 6.5-DEVELOPMENT,
cc 8.3 — `run.log`):

```
amdgpu_ucode_validate(4-byte blob) = 0 (0 == accepted)
validate passed: no minimum-size check (DF-1946 root), sdma_v3_0.c proceeds to read sub-header fields
RUN_EXIT=139 139=SIGSEGV          <- fault at the +16 read (ucode_version)
Segmentation fault (core dumped)
```

The first field read by sdma_v3_0.c:316 is the one that faults — the exact
access order of the kernel function.

## 3. Honest classification

* The guest kernel has `/boot/kernel/amdgpu.ko` but **no AMD PCI device**
  (`pciconf -l` → zero 0x1002 vendors; virtio-vga only — `env.txt`), so the
  kernel path cannot execute → `not_reproduced`, impact `none` (nothing
  demonstrated in-kernel). Same discipline as the family findings
  DF-1451/DF-1553/DF-1598 and DF-4027.
* Trigger requires replacing an `amdgpufw_*` firmware file (root) and
  booting/loading amdgpu on Tonga/Polaris/Fiji/Carrizo/Stoney hardware.
* Severity Low (privileged input; OOB read; fw_size inflation feeding the
  DF-1947 memcpy sink). CWE-125 / CWE-20.

## 4. Fix

`fix.diff`: reject any SDMA firmware whose `datasize <
sizeof(struct sdma_firmware_header_v1_0)` before the sub-header reads
(sdma_v3_0.c:312-317). This is the v3 twin of the DF-1598 fix; the shared
root fix (full header cross-validation in `amdgpu_ucode_validate`) is
DF-1946's scope, and the memcpy sink hardening is DF-1947's — deliberately
not duplicated here.

fix_status: not_testable (no AMD hardware on the guest; the guard is trivially
exercised by the harness's accepted-then-rejected sequence).

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