amdgpu_uvd sw_init/resume trust unvalidated firmware ucode extent fields (ucode_size_bytes/ucode_array_offset_bytes) β sw_init bo_size u32 wrap, resume ~4GiB OOB read past the firmware blob and underflowed OOB memset_io (DF-1946 family instance)
Summary
The only gate is amdgpu_ucode_validate (equality-only); the extent fields are then consumed unbounded: the bo_size '+8' wraps to 0 at 0xFFFFFFF8 (bo allocated with no firmware space); resume does memcpy_to_io(fw->data+offset, 0xFFFFFFF8) - a ~4GiB OOB read; and 'size -= ucode_size_bytes' underflows into an OOB memset_io write. Root-controlled firmware install path (malicious/supply-chain). Guest deterministic arithmetic replica. Extent-bound fix (resume inherits).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4405 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| fw_hdr_overflow.c | β | 4.2 KB | view raw | |
| build.sh | β | 118 B | view raw | |
| run.sh | β | 122 B | view raw | |
| build.log | β | 70 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| run.2.log | β | 1.2 KB | view raw | |
| env.txt | β | 708 B | view raw | |
| fix.diff | β | 884 B | view raw | |
| verdict.json | β | 2.9 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw |
DF-4405 β amdgpu_uvd_sw_init/amdgpu_uvd_resume trust unvalidated firmware header extent fields (DF-1946 family instance)
File: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:195-202 (only gate),
:250-253 (bo_size wrap), :403-411 (OOB copy / underflowed memset).
Severity: Low (requires attacker-installed firmware β root-only supply
path β on AMD hardware) Β· Confidence: certain (arithmetic demo +
line-certain inspection; family root = DF-1946 in amdgpu_ucode.c:251).
amdgpu_ucode_validate() only checks fw->datasize == hdr->size_bytes
(sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260). The UVD loader then trusts
hdr->ucode_size_bytes and hdr->ucode_array_offset_bytes without any bound
against the blob:
amdgpu_uvd.c:253βAMDGPU_GPU_PAGE_ALIGN(ucode_size_bytes + 8): withucode_size_bytes = 0xFFFFFFF8the u32+8wraps to 0 and the UVD bo is allocated without any firmware space;amdgpu_uvd.c:405-407β resume copiesucode_size_bytesbytes fromfw->data + ucode_array_offset_bytesinto VRAM: with the same header that is a ~4 GiB OOB read past a 32 KiB kernel firmware buffer;amdgpu_uvd.c:408-411βsize -= ucode_size_bytesunderflows and the followingmemset_io(ptr, 0, size)writes past the bo's ioremap extent.
Build
cc -O2 -Wall -o fw_hdr_overflow fw_hdr_overflow.c # or ./build.sh (guest)
Run
./fw_hdr_overflow # or ./run.sh (guest)
Expected output
Exact-arithmetic replica showing: amdgpu_ucode_validate returns 0
(ACCEPTED) for a 32 KiB blob whose header claims ucode_size_bytes =
0xFFFFFFF8 at offset 0x1000; the +8 wrap zeroes the bo_size term; resume
would copy 0xfffffff8 bytes (4 GiB) from the blob β 4,294,938,616 bytes past
its end β and then memset_io 8 bytes past the bo end. Exit 0.
Not-a-PoC note
Executing the real path needs an AMD GPU and a root-installed malicious
firmware file (/boot/modules/amdgpufw_*_uvd); the audit guest has neither
the hardware (QEMU bochs VGA only, no /dev/dri) nor, as policy, root-attacker
scope. Status: inconclusive; the arithmetic is deterministic C semantics and
is demonstrated in full (see run.log).
DF-4405 β VERDICT
Finding: the UVD firmware loader in sys/dev/drm/amd/amdgpu/amdgpu_uvd.c
consumes common_firmware_header.ucode_size_bytes and
.ucode_array_offset_bytes with no bound against the actual firmware blob β
the only gate, amdgpu_ucode_validate() (sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260),
checks a different field (fw->datasize == hdr->size_bytes).
Verdict: inconclusive β the kernel path cannot be executed on the audit
guest (no AMD GPU: pciconf shows only QEMU bochs VGA, /dev/dri absent,
amdgpu.ko loads but never attaches) and triggering it additionally requires
a root-installed malicious firmware file. The defect itself is certain:
amdgpu_uvd.c:253βbo_size += AMDGPU_GPU_PAGE_ALIGN(le32_to_cpu(hdr->ucode_size_bytes) + 8):ucode_size_bytes + 8is u32 arithmetic; at0xFFFFFFF8it wraps to 0 and the UVD bo is created without firmware space (demo: run.log, sw_init block).amdgpu_uvd.c:405-407β resume (whensaved_bo == NULL, the normal first-resume path for direct-load firmware) executesmemcpy_toio(cpu_addr, fw->data + offset, ucode_size_bytes)β a 4 GiB OOB read past thefirmwarebuffer for the same header (demo: run.log, resume block: 4,294,938,616 bytes past a 32 KiB blob).amdgpu_uvd.c:408-411βsize -= ucode_size_bytesunderflows u32 and the subsequentmemset_io(ptr, 0, size)runs past the bo's mapped extent.
This is the amdgpu_uvd.c instance of the DF-1946 family (root:
amdgpu_ucode.c:251; siblings DF-1164 gfx_v7_0, DF-1451 sdma_v4_0,
DF-1598 sdma_v2_4, DF-1854 psp_asd β¦). Pass 1 of this file did not file it
(only DF-1595/DF-1596 exist for this file).
Severity rationale
Low: the attacker must control a firmware file under /boot/modules/
(root-only write) and the machine must have AMD hardware. The impact if those
preconditions hold is a kernel OOB read of arbitrary length and an OOB
memset_io β memory corruption β so the family fix is worth backporting, but
the privilege model keeps it Low.
Attempts
One build+run cycle on the guest (exact arithmetic replication, deterministic, exit 0, two runs β run.log, run.2.log). No kernel execution possible.
Fix
fix.diff (git-apply clean) bounds the ucode extent against
adev->uvd.fw->datasize in sw_init, immediately after
amdgpu_ucode_validate() β the same style as the family fix; this also
inoculates resume (firmware blob is immutable between init and resume).
A patched-kernel run is not possible on this guest (no AMD GPU), so
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.
Fix verification
not_testablefix.diff bounds the ucode extent in sw_init (which resume then inherits, blob immutable); git apply --check clean; patched-kernel A/B impossible on this guest (no AMD GPU).
fix.diff; git apply --check; VERDICT.md severity/family rationale
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log/run.2.log (validate=ACCEPTED, +8 wrap -> PAGE_ALIGN(0)=0, 4GiB OOB read length, memset underflow); VERDICT.md; fix.diff (git apply --check clean)
PoC changes
Trigger written fresh (no seed): deterministic u32 arithmetic replica of both consumer sites for a header that passes amdgpu_ucode_validate; initial value 0xFFFFFF00 corrected to 0xFFFFFFF8 to demonstrate the clean +8 wrap.
Verified recommended fix
In amdgpu_uvd_sw_init, after amdgpu_ucode_validate, reject firmware whose (u64)ucode_array_offset_bytes + ucode_size_bytes > fw->datasize (see fix.diff); mirrors the DF-1946 family fix.
Verdict
amdgpu_uvd.c consumes common_firmware_header.ucode_size_bytes/ucode_array_offset_bytes without bounding them against the firmware blob: amdgpu_uvd.c:253 PAGE_ALIGN(ucode_size_bytes+8) wraps to 0 at 0xFFFFFFF8 (bo allocated with no fw space), amdgpu_uvd.c:405-407 resume memcpy_toio performs a ~4GiB OOB read past the kernel firmware buffer, amdgpu_uvd.c:408-411 size-=ucode_size_bytes underflows into an OOB memset_io. Only gate, amdgpu_ucode_validate (amdgpu_ucode.c:251-260), checks a different field (datasize==size_bytes). DF-1946 family instance in this file, unfiled by pass 1. Runtime execution impossible on the audit guest (no AMD GPU, /dev/dri absent) and requires root-installed firmware; exact arithmetic demonstrated deterministically (run.log): certain defect, inconclusive runtime.
No comments yet.