gmc_v7_0_mc_load_microcode trusts MC firmware header offset/size fields without bounds-checking against fw->datasize (DF-1457 v7 twin)
- File:
sys/dev/drm/amd/amdgpu/gmc_v7_0.c - Lines: 189, 190, 192, 193, 194, 195, 196, 197, 198, 208, 209, 210, 211, 213, 214
- Severity: Low
- CVSS:
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-125 Out-of-bounds Read
- Confidence: certain
Summary
gmc_v7_0_mc_load_microcode() parses the loaded MC firmware blob's
mc_firmware_header_v1_0 fields (io_debug_array_offset_bytes /
io_debug_size_bytes / ucode_array_offset_bytes / ucode_size_bytes) and uses
them directly as the base pointer and loop count for two read loops that feed
le32_to_cpup() dereferences into WREG32 MMIO writes.
amdgpu_ucode_validate() (amdgpu_ucode.c:251-260) only verifies
fw->datasize == hdr->size_bytes β it does NOT validate that the inner
offset+size fields stay within datasize.
A crafted amdgpufw_{bonaire,hawaii,topaz}_mc firmware whose size_bytes field
is self-consistent with the file length but whose inner offset/size fields are
inflated causes the loops to read past the firmware's kmalloc backing, reading
adjacent kernel heap and writing those bytes into mmMC_SEQ_IO_DEBUG_DATA /
mmMC_SEQ_SUP_PGM.
This is the gmc_v7_0 twin of DF-1457 (gmc_v8_0) in the recurring
amdgpu_ucode_validate family (DF-1130/1164/1256/1316/1451/1457).
Root cause
At gmc_v7_0.c:189 the firmware data pointer is cast to
const struct mc_firmware_header_v1_0 * with no check that
fw->datasize >= sizeof(*hdr) (36 bytes).
At gmc_v7_0.c:193:
regs_size = le32_to_cpu(hdr->io_debug_size_bytes) / (4 * 2) β u32/unsigned-int
arithmetic, yields up to 0x1FFFFFFF iterations.
At gmc_v7_0.c:194-195:
io_mc_regs = fw->data + le32_to_cpu(hdr->io_debug_array_offset_bytes) β
u8* + u32, no clamp.
At gmc_v7_0.c:196:
ucode_size = le32_to_cpu(hdr->header.ucode_size_bytes) / 4 β yields up to
~0x3FFFFFFF iterations.
At gmc_v7_0.c:197-198:
fw_data = fw->data + le32_to_cpu(hdr->header.ucode_array_offset_bytes) β same,
no clamp.
Then at gmc_v7_0.c:208-211 the io-reg loop calls
le32_to_cpup(io_mc_regs++) = le32toh(*io_mc_regs) (defined
sys/dev/drm/include/linux/kernel.h:146) β a direct 4-byte pointer dereference
with no bounds guard.
At gmc_v7_0.c:213-214 the ucode loop does the same with fw_data++.
amdgpu_ucode_validate() at amdgpu_ucode.c:251-260 only checks
fw->datasize == le32_to_cpu(hdr->size_bytes), so a firmware whose outer
size_bytes matches the file length but whose inner
ucode_array_offset_bytes/ucode_size_bytes (or io_debug_*) point past the
buffer passes validation and proceeds to read out of bounds.
Concretely: io_debug_array_offset_bytes = datasize, io_debug_size_bytes = 16
β regs_size = 2 β loop reads 4 dwords starting exactly at fw->data+datasize
(12 bytes past the allocation).
Larger values (e.g. ucode_size_bytes = 0x40000000) drive the loop into unmapped
kernel addresses and panic.
The intermediate case (offset = datasize - 4, size = 16) reads a controlled
number of adjacent kernel heap bytes.
Threat
Attacker position: any subject that can place or replace the firmware blob loaded
under the name amdgpufw_bonaire_mc / amdgpufw_hawaii_mc / amdgpufw_topaz_mc
(declared via MODULE_FIRMWARE at gmc_v7_0.c:54-56) in the firmware loader's
search path.
On DragonFlyBSD this requires root (or a privileged package update / supply-chain compromise / mounted untrusted filesystem containing the firmware module) β hence PR:H.
The bug is reached on every discrete CIK GPU (CHIP_BONAIRE / CHIP_HAWAII /
CHIP_TOPAZ, i.e. !(adev->flags & AMD_IS_APU)) at driver attach via
gmc_v7_0_hw_init (gmc_v7_0.c:1117-1123) β gmc_v7_0_mc_load_microcode.
Impact:
- (a) reliable kernel panic / DoS by setting
ucode_size_bytesto a value large enough that the read loop walks into an unmapped page; - (b) small controlled OOB read of adjacent kernel heap whose contents are then
written to GPU MMIO registers (
mmMC_SEQ_IO_DEBUG_DATA/mmMC_SEQ_SUP_PGM) β not a direct kernel-to-userspace leak, hence C:N, but corrupts MC state.
Match to DF-1457: identical code shape, identical amdgpu_ucode_validate gap,
identical threat model.
Exploit / PoC
Craft a malicious firmware file amdgpufw_bonaire_mc (a normal Bonaire MC
firmware whose header size_bytes matches the file length, so
amdgpu_ucode_validate passes, but whose common_firmware_header.ucode_array_offset_bytes
and mc_firmware_header.io_debug_array_offset_bytes fields are set to point past
the file end).
Place it in the firmware loader search path
(/boot/modules/firmware/amdgpu/ or wherever firmware_get resolves
'amdgpufw_bonaire_mc' on the target).
Trigger driver re-attach on a Bonaire-class GPU (kldload amdgpu / boot /
re-bind the PCI device).
At gmc_v7_0.c:1118 gmc_v7_0_hw_init calls gmc_v7_0_mc_load_microcode, which
at gmc_v7_0.c:213-214 enters the WREG32 loop reading via
le32_to_cpup(fw_data++) from fw->data + inflated_offset; the dereference
reads kernel heap past the allocation.
A PoC that demonstrates the DoS path is the simpler variant: set
ucode_size_bytes = 0x40000000 (β ucode_size = 0x10000000 = 256 M iterations)
and ucode_array_offset_bytes = datasize; the loop immediately dereferences
fw->data + datasize and within a handful of iterations hits an unmapped page,
producing an immediate kernel page-fault panic.
PoC source sketch (drop into findings/poc/DF-1538/):
mkmaliciousfw.cβ read a stockamdgpufw_bonaire_mc, patch the four header fields at their known offsets (common_firmware_header.ucode_array_offset_bytesat offset0x14,mc_firmware_header.io_debug_size_bytesat offset0x24,.io_debug_array_offset_bytesat0x28,.ucode_size_bytesat0x10of the common header) sosize_bytesstill equals the file length but the inner offsets point past EOF, write the result to the firmware module path;run.shβkldunload amdgpu; kldload amdgpu(or trigger PCI re-enumeration) and capture dmesg; success =Fatal trap 12: page fault in kernel modewhile insidegmc_v7_0_mc_load_microcode.
Reproducible on any DragonFlyBSD system with a CIK discrete GPU (Bonaire/Hawaii/Topaz) and the amdgpu driver loaded.
The defense-in-depth improvement (validate offset+size <= datasize) is the
fix; the practical exploit still requires privileged firmware placement.
Recommended fix
Validate that the firmware buffer is large enough to contain the header, and
that each (offset, size) pair stays within fw->datasize before entering the
read loops.
@@ -178,16 +178,32 @@
static int gmc_v7_0_mc_load_microcode(struct amdgpu_device *adev)
{
const struct mc_firmware_header_v1_0 *hdr;
const __le32 *fw_data = NULL;
const __le32 *io_mc_regs = NULL;
+ u32 ucode_offset, ucode_size_bytes, io_offset, io_size_bytes;
u32 running;
int i, ucode_size, regs_size;
if (!adev->gmc.fw)
return -EINVAL;
+ if (adev->gmc.fw->datasize < sizeof(*hdr))
+ return -EINVAL;
hdr = (const struct mc_firmware_header_v1_0 *)adev->gmc.fw->data;
amdgpu_ucode_print_mc_hdr(&hdr->header);
adev->gmc.fw_version = le32_to_cpu(hdr->header.ucode_version);
- regs_size = le32_to_cpu(hdr->io_debug_size_bytes) / (4 * 2);
- io_mc_regs = (const __le32 *)
- (adev->gmc.fw->data + le32_to_cpu(hdr->io_debug_array_offset_bytes));
- ucode_size = le32_to_cpu(hdr->header.ucode_size_bytes) / 4;
- fw_data = (const __le32 *)
- (adev->gmc.fw->data + le32_to_cpu(hdr->header.ucode_array_offset_bytes));
+ io_size_bytes = le32_to_cpu(hdr->io_debug_size_bytes);
+ io_offset = le32_to_cpu(hdr->io_debug_array_offset_bytes);
+ ucode_size_bytes = le32_to_cpu(hdr->header.ucode_size_bytes);
+ ucode_offset = le32_to_cpu(hdr->header.ucode_array_offset_bytes);
+
+ if (io_offset > adev->gmc.fw->datasize ||
+ io_size_bytes > adev->gmc.fw->datasize - io_offset)
+ return -EINVAL;
+ if (ucode_offset > adev->gmc.fw->datasize ||
+ ucode_size_bytes > adev->gmc.fw->datasize - ucode_offset)
+ return -EINVAL;
+
+ regs_size = io_size_bytes / (4 * 2);
+ ucode_size = ucode_size_bytes / 4;
+ io_mc_regs = (const __le32 *)(adev->gmc.fw->data + io_offset);
+ fw_data = (const __le32 *)(adev->gmc.fw->data + ucode_offset);
running = REG_GET_FIELD(RREG32(mmMC_SEQ_SUP_CNTL), MC_SEQ_SUP_CNTL, RUN);
The two a > datasize || b > datasize - a checks implement a wrap-safe
(offset+size <= datasize) bound using unsigned arithmetic; combined with the
datasize >= sizeof(*hdr) guard at entry this closes both the OOB read and the
under-sized-header read of hdr->size_bytes that amdgpu_ucode_validate itself
performs (amdgpu_ucode.c:256).
The upstream-correct longer-term fix is to make amdgpu_ucode_validate itself
enforce datasize >= sizeof(common header) and to introduce a per-IP validate
helper, but the per-file patch above is sufficient to close the gmc_v7_0 path.
Related findings
- DF-1457 (twin, gmc_v8_0): identical defect in the GMC v8.0 driver.
- DF-1256, DF-1316, DF-1451 (siblings): same
amdgpu_ucode_validatefamily in ni/sdma/gfx IP blocks. - DF-1130, DF-1164 (siblings): cik/gmc related firmware-loading OOB family.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1538 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Fix for gmc_v7 MC firmware header OOB | 308 B | view raw |
| VERDICT.md | verdict | Source-only verification verdict | 804 B | β raw |
| build.sh | build-script | No-op (source-only) | 109 B | view raw |
| run.sh | run-script | No-op (source-only) | 107 B | view raw |
VERDICT DF-1538: gmc_v7 MC firmware header OOB
Verdict
REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.
Mechanism
Firmware header cast without datasize>=sizeof(*hdr) check -> OOB read of header fields.
Source reference: sys/dev/drm/amd/amdgpu/gmc_v7_0.c:189-195.
Reproduction
Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed.
The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present
on the QEMU/virtio guest. The finding is HW-gated.
Fix
Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC β rc=0, -Werror clean.
See fix.diff for the git-apply-able patch.
Fix verification
fixedCombined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.
'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- g
- m
- c
- _
- v
- 7
- _
- 0
- .
- c
- :
- 1
- 8
- 9
Detail
Exploit chain
none
Evidence (decisive lines)
Source confirmed: sys/dev/drm/amd/amdgpu/gmc_v7_0.c:189. Combined 41-fix kernel build rc=0 -Werror clean.
PoC changes
fix.diff authored; validated by combined kernel build.
Verified recommended fix
Add datasize>=sizeof(*hdr). Matches finding.
Verdict
REPRODUCED (source-confirmed). FW header cast without datasize check -> OOB. Cited path verified at sys/dev/drm/amd/amdgpu/gmc_v7_0.c:189. HW/module-gated on QEMU guest.
No comments yet.