amdgpu_cgs_get_firmware_info CP_MEC branch: info->image_size = jt_offset << 2 from an unvalidated firmware-header dword β SMU-side OOB DMA read of up to ~4 GB from the staged fw_buf window (DF-4774 shape on the CP branch, kernel-side fully in-bounds)
Summary
THE DF-4774 FOLLOW-UP found the same unchecked-header-window shape on the CP_MEC branch: image_size = jt_offset<<2 (a raw MEC header dword) never bounded against the file or staged copy - it flows into the smu7 TOC entry data_size_byte and PPSMC_MSG_LoadUcodes commands the SMU DMA to read an 8x-oversized window from fw_buf staged at only ALIGN(ucode)+ALIGN(jt). HARNESS-REPRODUCED 3/3 with verbatim-function methodology (mock GTT + mock SMU DMA): crafted self-consistent header -> 57344 bytes SMU-side OOB DMA read beyond the window + unmapped-page fault class, with kernel-side OOB reads = 0 (distinguishes from DF-1947); patched twin rejects at construction. Root-planted fw on SMU-load asics, at attach. Bound-jt_offset-at-construction fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4862 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| cgs_mec_jt_oob.c | β | 24.9 KB | view raw | |
| cgs_mec_jt_oob_patched.c | β | 23.0 KB | view raw | |
| build.sh | β | 188 B | view raw | |
| run.sh | β | 168 B | view raw | |
| build.log | β | 447 B | view raw | |
| run.log | β | 3.4 KB | view raw | |
| run.2.log | β | 1.8 KB | view raw | |
| run.3.log | β | 1.8 KB | view raw | |
| env.txt | β | 299 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 5.1 KB | β raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 6.0 KB | view raw |
DF-4862 β amdgpu_cgs CP_MEC image_size = jt_offset << 2 unbounded (SMU-side OOB DMA read)
What
amdgpu_cgs_get_firmware_info() non-SMU branch
(sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:234-235):
if (CGS_UCODE_ID_CP_MEC == type)
info->image_size = le32_to_cpu(header->jt_offset) << 2;
jt_offset is a raw dword from the MEC gfx firmware file header
(gfx_firmware_header_v1_0.jt_offset, amdgpu_ucode.h:64). It is never
bounded β not against ucode_size_bytes, not against the file, not against
the staged copy window. <<2 wraps mod 2^32, so any value 0..0xFFFFFFFC can
be produced. This is the same unchecked-header-window shape as DF-4774
(which bounded the SMU branch); the CP branch was missed.
Consumer chain (why it matters)
smu7_populate_single_firmware_entry()smu7_smumgr.c:305-327:entry->data_size_byte = info.image_size(:327)smu7_request_smu_load_fw()smu7_smumgr.c:405populates the CP_MEC TOC entry,:424memcpy_toios the TOC to SMU-visible DRAM,:431sendsPPSMC_MSG_LoadUcodesβ after which the SMU DMA engine copiesdata_size_bytebytes fromentry.image_addr, the MEC1 window insideadev->firmware.fw_bufstaged byamdgpu_ucode_init_bo()(amdgpu_ucode.c:449-485) asALIGN(ucode_size_bytes,PAGE)(+ JT pages).- With a crafted MEC header the SMU reads far beyond the staged window: adjacent GTT pages are disclosed into SMC SRAM (device-side), and the unmapped range beyond triggers a GPU page fault / SMU hang (driver-load failure β repeated GPU resets β system-level DoS).
- The only validation anywhere on the path is
amdgpu_ucode_validate()'sdatasize == size_bytes(amdgpu_ucode.c:251-259, DF-1946).
Distinct from DF-1947 (staging memcpy OOB in amdgpu_ucode.c): scenario [2]
of the harness has zero kernel-side OOB reads β the file is entirely
self-consistent β yet the SMU-side window is 8x oversized. The kernel never
notices; the GPU-side DMA does the damage.
Build / run / expected
sh build.sh # cc -O0 -g -Wall both binaries (baseline + patched twin) sh run.sh # runs baseline then patched twin
Expected (baseline):
[1]legit header (jt_offset=0x300 dwords):data_size_byte=0xc00, SMU DMA fully in-bounds β no false positive.[2]crafted header (jt_offset=0x4000 dwords, everything else sane, validate PASSes):data_size_byte=0x10000vs staged window0x2000βRESULT: SMU-SIDE OOB DMA READ CONFIRMED: 57344 bytes beyond the staged fw_buf window + unmapped-page fault class, withkernel-side OOB reads during init_bo/patch_jt: 0 bytes.[3]jt_offset=0x3F000000: child killed by SIGSEGV β the kernel-sidepatch_jtwild source (DF-1947 overlap) faults first at driver load; shows the extreme end of the same unvalidated field.[4](also DF-4863):ucode[AMDGPU_UCODE_ID_MAXIMUM].fwaliasesfirmware.fw_bufβALIAS CONFIRMED, garbage propagated to caller.
Expected (patched twin, fix.diff at the construction): [1] still loads
identically; [2] populate CP_MEC entry: -22 (rejected at the cgs
construction β FIX VALIDATED); [4] -22 one-past-end index rejected.
Harness methodology
No AMD GPU exists on the audit guest, so (per DF-4774 / DF-4665 / DF-3683 /
DF-2096) the harness copies the cited function bodies 1:1 β
fw_type_convert, amdgpu_get_firmware_version, the non-SMU branch of
amdgpu_cgs_get_firmware_info, amdgpu_ucode_validate, the staging math of
amdgpu_ucode_init_single_fw/amdgpu_ucode_patch_jt/amdgpu_ucode_init_bo,
and smu7_populate_single_firmware_entry β with 1:1 struct layouts
(_Static_asserted against the real field offsets) and a mock GTT/SMU-DMA
engine whose mapping model (staged BO + adjacent mapped page + unmapped
guard) mirrors the real GPU address space. [VERBATIM] markers in the
source cite each block's kernel lines.
Deterministic: 3/3 runs identical (run.log, run.2.log, run.3.log).
DF-4862 β VERDICT
Status: reproduced (verbatim-function harness, 3/3 deterministic runs) Impact: dos (device-side OOB DMA read of up to ~4 GB from the staged firmware window + SMU/GPU fault β driver-load DoS; limited device-side disclosure of adjacent GTT pages into SMC SRAM, no userland readback primitive absent a second bug β same ceiling/convention as DF-4774) Severity filed: Medium Β· bucket: kernleak Β· confidence: certain
What was run
build.sh && run.sh on the DragonFly 6.5-DEVELOPMENT guest
(cc 8.3 [DragonFly]), baseline + patched twin, plus two extra baseline
runs for determinism. No AMD GPU is present on the guest; per house
methodology (DF-4774, DF-4665, DF-3683, DF-2096) the harness executes the
verbatim kernel function bodies with 1:1 struct layouts against a mock
GTT/SMU-DMA model.
Baseline observations (run.log)
- Scenario [2] β crafted MEC header with
jt_offset=0x4000dwords,jt_size=0x100dwords,ucode_size_bytes=0x1000, file 0x12000 bytes,size_bytes == datasizesoamdgpu_ucode_validatePASSES: kernel-side OOB reads during init_bo/patch_jt: 0 bytesβ the file is fully self-consistent, so nothing in the kernel staging path (includingpatch_jt's source read) ever leaves the allocation. This is what makes the finding distinct from DF-1947 (staging memcpy OOB) β that bug's kernel-side read window is never entered.- TOC entry:
data_size_byte=0x10000 (65536)withimage_addrpointing at the MEC1 staged window[..0x3000, ..0x5000)(0x2000 bytes). - Mock SMU DMA (walks the read like the SMU would, through the GPU
mapping):
8192 bytes in-bounds, 57344 bytes BEYOND the staged windowand the walk hits the unmapped page at +0x13000 β GPU page fault / SMU hang class. - Marker:
RESULT: SMU-SIDE OOB DMA READ CONFIRMED: 57344 bytes beyond the staged fw_buf window + unmapped-page fault class. - Scenario [1] β legit header (
jt_offset=0x300):0xc00bytes, fully in-bounds. No false positive. - Scenario [3] β
jt_offset=0x3F000000: the child dies on SIGSEGV inside the verbatimpatch_jtemulation because the JT source (fw->data + array_offset + jt_offset*4= ~4 GB past the blob) is unmapped β on real hardware this is the kernel-side panic through the DF-1947 site at driver attach. Included to show the field is unbounded end-to-end; the clean DF-4862 window is scenario [2], which needs no kernel-side violation at all. image_sizefromjt_offset<<2also wraps mod 2^32 (0x3F000000<<2 = 0xFC000000), i.e. the reported window is attacker-chosen arbitrarily.
Root cause chain (path:line)
sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:234-235βinfo->image_size = le32_to_cpu(header->jt_offset) << 2;with no bound againstdata_size(ucode_size_bytes, line 221), the file, or the staged window.sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-259β the only validation isdatasize == size_bytes(DF-1946).sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:449-485β the staged MEC1 window isALIGN(ucode_size_bytes,PAGE)+ALIGN(jt_size<<2,PAGE); nothing checks the TOC-reported window against it.sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:305-327βentry->data_size_byte = info.image_size;:405CP_MEC entry;:424TOC β SMU DRAM;:431PPSMC_MSG_LoadUcodesβ SMU DMA ofdata_size_bytebytes fromimage_addr.- Reachability:
gfx_v8_0.c:1204registersucode[CP_MEC1].fwon exactly the SMU-load asics (Topaz/Tonga/Fiji/Polaris/VegaM peramdgpu_ucode.c:290-304), driver attach β powerplaystart_smuβsmu7_request_smu_load_fw. Threat: crafted root-placed MEC firmware file (or compromised firmware source) β same threat model as DF-4774.
Fix validation (fix.diff)
Patched twin = every byte identical except the bound at the construction
(jt_offset<<2 > ucode_size_bytes β -EINVAL, plus the DF-4863 index guard).
- Scenario [1] legit header: unchanged, 0xc00 in-bounds, still loads.
- Scenario [2]: populate CP_MEC entry: -22 (rejected at the cgs
construction β FIX VALIDATED) β no TOC entry, no SMU DMA attempted.
- 3/3 runs. Full kernel rebuild not performed: no AMD GPU exists on the
audit guest, so the verbatim-function harness is the faithful validation
surface (same methodology as DF-4774, whose fix.diff used the identical
pattern for the SMU branch).
Notes
hwmgr->not_vfVFinfo.image_size -= 20underflow (smu7_smumgr.c:326) is a pre-existing wart noted in DF-4774 β not re-filed, not part of this fix.- The JT1/JT2 branch (
amdgpu_cgs.c:223-227) is self-consistent with the staging layout (jt windowALIGN(jt_size<<2,PAGE)), no finding there. CGS_UCODE_ID_GMCON_RENGone-past-end aliasing is filed separately as DF-4863 (scenario [4] of the same harness).
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
fixedPatched-twin harness (fix.diff bound jt_offset<<2 <= ucode_size_bytes at the amdgpu_cgs.c:234-235 construction + the DF-4863 index guard; every other byte identical) rejects the crafted header with -EINVAL before any TOC entry is populated (no SMU DMA attempted), while the legit-header scenario [1] still loads 3072 bytes fully in-bounds. 3/3 deterministic runs. Full kernel rebuild not performed: no AMD GPU exists on the audit guest, so the verbatim-function harness is the faithful validation surface (same methodology as DF-4774, whose SMU-branch fix used the identical pattern).
findings/poc/DF-4862/run.log patched-twin sections: scenario [1] 'SMU DMA: 3072 bytes in-bounds, 0 bytes BEYOND' (unchanged) and scenario [2] 'populate CP_MEC entry: -22 (rejected at the cgs construction - FIX VALIDATED)'; fix.diff passes git apply --check against sys/.
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:234-235
- sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:219-231
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-259
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:449-485
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:305-327
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:405
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:424-431
- sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1204
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:61-66
Detail
Exploit chain
crafted MEC gfx firmware file (root-placed /usr/lib/firmware/amdgpu, or compromised fw source) -> driver attach on SMU-load asics (Topaz/Tonga/Fiji/Polaris/VegaM: gfx_v8_0.c:1204 registers ucode[CP_MEC1].fw) -> amdgpu_ucode_validate passes (datasize==size_bytes only) -> amdgpu_ucode_init_bo stages ALIGN(ucode_size,PAGE)+ALIGN(jt<<2,PAGE) -> powerplay start_smu -> smu7_request_smu_load_fw populates CP_MEC TOC entry with data_size_byte = jt_offset<<2 (unbounded) -> TOC uploaded, PPSMC_MSG_LoadUcodes -> SMU DMA engine reads data_size_byte bytes from the MEC1 staged page: up to ~4 GB window; adjacent GTT pages disclosed into SMC SRAM (device-side, no userland readback absent a second bug), unmapped range -> GPU page fault / SMU hang / repeated failed loads -> system-level DoS. Ceiling: DoS + limited device-side disclosure (kernleak family convention, same as DF-4774).
Evidence (decisive lines)
findings/poc/DF-4862/run.log: '[2] ... TOC entry[0]: data_size_byte=0x10000 ... staged MEC window=[0x100003000,0x100005000)' then 'SMU DMA: 8192 bytes in-bounds, 57344 bytes BEYOND the staged window (hit UNMAPPED GPU page at +0x13000 -> GPU page fault / SMU hang)' and 'RESULT: SMU-SIDE OOB DMA READ CONFIRMED', with 'kernel-side OOB reads during init_bo/patch_jt: 0 bytes (fully in-bounds)'; '[1]' legit header 3072 bytes fully in-bounds; patched-twin '[2]' shows 'populate CP_MEC entry: -22 (rejected at the cgs construction - FIX VALIDATED)' and '[1]' unchanged. Identical markers in run.2.log and run.3.log.
PoC changes
Harness authored fresh for this finding (no prior seed): verbatim fw_type_convert + amdgpu_get_firmware_version + non-SMU branch of amdgpu_cgs_get_firmware_info + amdgpu_ucode_validate + init_single_fw/patch_jt/init_bo staging math + smu7_populate_single_firmware_entry; _Static_assert-proven struct offsets; mock GTT (staged BO + adjacent mapped page + PROT_NONE guard) walked by a mock SMU DMA engine. Patched twin generated from the same source with only the two fix.diff checks added.
Verified recommended fix
In amdgpu_cgs_get_firmware_info (amdgpu_cgs.c:234-235), bound the CP_MEC window: reject unless (uint64_t)jt_offset<<2 <= ucode_size_bytes (data_size) before assigning info->image_size; also range-check fw_type_convert's return before indexing adev->firmware.ucode[] (fix.diff).
Verdict
Harness-verified on the DragonFly 6.5-DEVELOPMENT guest using verbatim function bodies with 1:1 struct layouts (no AMD GPU on the audit guest; methodology identical to DF-4774/DF-4665/DF-3683): amdgpu_cgs_get_firmware_info's CP_MEC branch (amdgpu_cgs.c:234-235) sets info->image_size = jt_offset<<2 from an unvalidated firmware-header dword. A crafted but fully self-consistent MEC header (jt_offset=0x4000 dwords, validate PASSES) yields TOC entry data_size_byte=0x10000 against a staged window of 0x2000 -> the SMU DMA engine (PPSMC_MSG_LoadUcodes via smu7_smumgr.c:405/424/431) reads 57344 bytes beyond the staged fw_buf window (adjacent GTT pages disclosed device-side, unmapped range -> GPU page fault / SMU hang -> driver-load DoS), with ZERO kernel-side OOB reads in the staging path (distinct from DF-1947: image_size<<2 up to 0xFFFFFFFC, wraps mod 2^32). Legit header fully in-bounds (no false positive). Patched twin (bound jt_offset<<2 <= ucode_size_bytes at the construction) rejects the crafted header before any TOC entry while the legit header still loads; 3/3 deterministic runs.
No comments yet.