smu7 SMC-firmware upload streams ucode payload from unchecked firmware-header offset/size: kernel heap OOB read up to 128 KB from ~4 GB past the blob (powerplay smu7 twin of DF-2065/DF-2096/DF-2104)
Summary
smu7_upload_smc_firmware_data's only validation is image_size <= SMU7_SMC_SIZE (the SRAM window), never a bound against fw->datasize; kptr=fw->data+ucode_array_offset_bytes and image_size are self-declared by the firmware file and amdgpu_ucode_validate only checks datasize==size_bytes. The dword loop reads up to 0x20000 bytes from up to 4GB past the allocation (wild base=panic; adjacent=heap streamed into SMC SRAM). REPRODUCED 3/3 via verbatim-function harness (adjacent OOB confirmed with canary pages; wild bases SIGSEGV); bound-at-pointer-construction fix validated in patched twin. Root file-placement trust root; VI-family attach path.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4774 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| smu7_fw_upload_oob.c | β | 13.8 KB | view raw | |
| smu7_fw_upload_oob_patched.c | β | 14.4 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 483 B | view raw | |
| run.log | β | 3.0 KB | view raw | |
| run.2.log | β | 3.0 KB | view raw | |
| run.3.log | β | 3.0 KB | view raw | |
| env.txt | β | 239 B | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| VERDICT.md | β | 5.2 KB | β raw | |
| README.md | β | 2.7 KB | β raw |
DF-4774 β smu7 SMC-firmware upload: unvalidated header offset/size β kernel heap OOB read
File: sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c (pass 2)
Function: smu7_upload_smu_firmware_image() :481-500 β smu7_upload_smc_firmware_data() :461-478
Twins (filed): DF-2065 (radeon si_smc.c), DF-2096 (amdgpu ci_smc.c), DF-2104 (radeon ci_smc.c) β this is the unfiled powerplay smu7 leg (Tonga/Fiji/Polaris/Topaz/VegaM).
Build
sh build.sh # cc -O0 -g -Wall: smu7_fw_upload_oob + smu7_fw_upload_oob_patched
Run
sh run.sh # baseline then patched twin
Expected output (baseline)
[1]legit header (off=0x400 size=0x800): 512 dwords streamed, 0 canary hits β sanity that the verbatim path works.[2]off = 0x1000(end of the 0x1000-byte blob, into the canary page),size = 0x1000:canary bytes in SMC sink : 1024,sink sample: cc000000 cc000001 ...,ADJACENT-OOB-READ CONFIRMEDβ kernel memory past the firmware allocation is streamed dword-by-dword into SMC SRAM via MMIO.[3]off = 0x40000000,size = 0x20000(max the length gate allows): child killed by SIGSEGV β kernel-panic equivalent wild read.[4]off = 0xFFFFF000(~4 GB): child killed by SIGSEGV.[5]the only check on the path (limit >= byte_count, smu7_smumgr.c:465) rejectssize > 0x20000β nothing bounds the base offset.
Expected output (patched twin β fix.diff bound check in amdgpu_cgs.c:461-472)
[2]/[3]/[4]:FIX VALIDATEDβ the construction is rejected (-EINVAL) before any read; 0 dwords streamed; no fault.[1]legit header still uploads (512 dwords) β no functional regression.
Threat
The SMC firmware file is loaded at powerplay start_smu (request_firmware at
amdgpu_cgs.c:437, validated only by amdgpu_ucode_validate = datasize == size_bytes,
amdgpu_ucode.c:251-259). ucode_array_offset_bytes/ucode_size_bytes are
self-declared by that file and reach the upload loop unchecked β a crafted
firmware file (root-placed in the firmware path) makes the kernel read up to
128 KB from up to ~4 GB past the firmware blob. Impact: panic-class DoS plus
adjacent-kernel-memory disclosure into GPU SMC SRAM. Requires an AMD VI GPU
(smu7 family) and control of the firmware file β same trust model as the filed
twin family.
Verdict
status=reproduced, impact=dos (wild read β panic; adjacent-read exfil into
device SRAM demonstrated; no userland readback primitive without further bugs).
Deterministic: 3/3 runs identical (run.log, run.2.log, run.3.log).
No AMD GPU on the audit guest β verbatim-function harness methodology
(identical to DF-4665/DF-3683/DF-2096). See VERDICT.md.
DF-4774 VERDICT β reproduced (harness), impact=dos (+device-directed disclosure)
What was verified
The exact kernel functions from sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c
(smu7_upload_smc_firmware_data :461-478 and smu7_upload_smu_firmware_image
:481-500, copied verbatim) were compiled on the DragonFly 6.5-DEVELOPMENT
guest against 1:1 struct layouts (common_firmware_header,
smc_firmware_header_v1_0, cgs_firmware_info, smu7_smumgr) and fed by the
verbatim SMU branch of amdgpu_cgs_get_firmware_info
(amdgpu_cgs.c:437-472) with amdgpu_ucode_validate (amdgpu_ucode.c:251-259)
as the only gate. MMIO writes are captured in a mock SMC SRAM sink; the
firmware blob is an mmap'd allocation followed by read-only canary pages
(unique 0xCCnnnnnn dwords) and a PROT_NONE guard page β modeling the
kernel heap allocation that request_firmware produces (fw->data,
fw->datasize).
Result (run.log; identical in run.2.log, run.3.log)
- Adjacent OOB read confirmed. With a crafted header
(
size_bytes == filesizeβ validate passes;ucode_array_offset_bytes= end of blob;ucode_size_bytes= 0x1000 β€ SMU7_SMC_SIZE), the verbatim upload loop streamed 1024/1024 canary dwords into the SMC SRAM sink (sink sample: cc000000 cc000001 cc000002 β¦). In the kernel this is adjacent heap memory disclosed dword-by-dword into GPU SMC SRAM viacgs_write_register(mmSMC_IND_DATA_11, *src++)(smu7_smumgr.c:470-471). - Wild-read panic class.
ucode_array_offset_bytes = 0x40000000(1 GB past) withucode_size_bytes = 0x20000β the maximum the length gate permits β kills the reading child with SIGSEGV. Same at 0xFFFFF000 (~4 GB, u32 near-wrap base). In-kernel equivalent: unrecoverable page fault at driver load β panic. - The only check is the length gate.
limit >= byte_count(smu7_smumgr.c:465,limit = SMU7_SMC_SIZE = 0x20000) rejectsucode_size_bytes > 0x20000but never examines the base offset, and nothing on the path comparesucode_array_offset_bytesorucode_size_bytesagainstfw->datasize.
Why this is a real kernel memory-safety bug
amdgpu_ucode_validate() proves only fw->datasize == hdr->size_bytes
(self-declared). amdgpu_cgs_get_firmware_info() then builds
info->kptr = fw->data + ucode_array_offset_bytes and
info->image_size = ucode_size_bytes (amdgpu_cgs.c:464-472) and
smu7_upload_smu_firmware_image() hands both to the upload loop
(smu7_smumgr.c:497). The kernel therefore dereferences a pointer/length pair
fully controlled by the firmware file, against an allocation whose size it
knows (fw->datasize) but never checks. Reached unconditionally at powerplay
start_smu for Tonga/Fiji/Polaris10/11/12/Topaz/VegaM
(polaris10_smumgr.c:209, tonga_smumgr.c:104, fiji/vegam/iceland equivalents).
Preconditions: AMD smu7-family GPU + attacker-supplied SMC firmware file (root on the box / malicious firmware package). Identical trust model and severity band as the filed twins DF-2065 (Medium), DF-2104 (Medium), DF-2096 (Low, amdgpu direct path).
Fix validation (patched twin)
smu7_fw_upload_oob_patched.c is byte-identical to the baseline except for
the fix.diff bound check inserted at the pointer construction (the exact
hunk proposed for amdgpu_cgs.c). All three attack scenarios print
FIX VALIDATED: the header is rejected with -EINVAL before any read,
0 dwords streamed, no fault; the legit-header scenario still uploads 512
dwords (no functional regression).
Note on patched [2] printing upload result: 0: this is verbatim kernel
behavior, not a harness artifact β smu7_upload_smu_firmware_image()
ignores the return of cgs_get_firmware_info() (smu7_smumgr.c:488-493),
so a rejected info lookup leaves info = {0} and the length-0 upload
"succeeds" with zero reads. The OOB read is gone; the pre-existing
error-swallowing wart (also noted for smu7_send_msg_to_smc) is a separate
reliability issue, not filed.
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-4665/DF-3683/DF-2096).
Exploit chain
crafted SMC firmware file (root-placed) β powerplay start_smu at driver
attach β request_firmware + amdgpu_ucode_validate (self-declared sizes pass)
β amdgpu_cgs_get_firmware_info builds fw->data + attacker_u32 /
attacker_u32 β€ 0x20000 β smu7_upload_smc_firmware_data reads up to 128 KB
OOB from the kernel heap β (a) wild base β panic; (b) adjacent base β
heap memory streamed into SMC SRAM (device-side disclosure; no userland
readback primitive absent a second bug). Ceiling: DoS + limited disclosure,
hence Medium.
Artifacts
smu7_fw_upload_oob.c/smu7_fw_upload_oob_patched.cβ sourcesbuild.sh,run.sh,build.log,run.log,run.2.log,run.3.log,env.txtfix.diffβ bound check at amdgpu_cgs.c:461-472 (never applied tosys/)
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 check at the amdgpu_cgs.c pointer construction, every other byte identical) rejects all three attack headers (-EINVAL) before any read: 0 dwords streamed, 0 canary hits, no SIGSEGV for the wild bases, while the legit-header scenario still uploads 512 dwords. 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-4665/DF-3683).
findings/poc/DF-4774/run.log patched-twin sections: 'FIX VALIDATED: upload rejected / no canary bytes reached the sink', 'FIX VALIDATED: wild-base upload rejected before any read (no fault)', 'FIX VALIDATED: near-wrap base rejected before any read'; scenario [1] legit header unchanged (512 dwords).
Confirmed kernel references
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:461-478
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:481-500
- sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c:497
- sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:437-472
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-259
- sys/dev/drm/amd/powerplay/smumgr/polaris10_smumgr.c:209
Detail
Exploit chain
crafted SMC firmware file (root-placed) -> powerplay start_smu at driver attach (polaris10_smumgr.c:209, tonga/fiji/vegam/iceland twins) -> request_firmware + amdgpu_ucode_validate (self-declared sizes pass) -> amdgpu_cgs_get_firmware_info builds kptr = fw->data + attacker u32 and image_size = attacker u32 <= 0x20000 -> smu7_upload_smc_firmware_data (smu7_smumgr.c:470-471) reads up to 128 KB past the kernel firmware allocation -> wild base: kernel panic (DoS); adjacent base: heap memory disclosed dword-by-dword into GPU SMC SRAM (device-side disclosure, no userland readback primitive absent a second bug). Ceiling: DoS + limited disclosure.
Evidence (decisive lines)
findings/poc/DF-4774/run.log: '[2] ... canary bytes in SMC sink : 1024 / sink sample: cc000000 cc000001 ... / ADJACENT-OOB-READ CONFIRMED'; '[3][4] child killed by SIGSEGV'; '[5]' shows the length gate is the only check; patched-twin sections show 3x 'FIX VALIDATED' with 0 dwords streamed. Identical markers in run.2.log and run.3.log.
PoC changes
Harness authored fresh (no prior seed): verbatim smu7_upload_smc_firmware_data + smu7_upload_smu_firmware_image + amdgpu_cgs SMU-branch construction + amdgpu_ucode_validate; mock MMIO sink emulating SMC_IND_INDEX/DATA_11 auto-increment; firmware blob mmap'd with RO canary pages (0xCCnnnnnn dwords) and a PROT_NONE guard. Fixed during bring-up: missing stdbool/signal includes, macro-before-use, ambiguous child exit codes, buffered-stdout duplication across fork (setvbuf), and one stale-tarball transfer that briefly rebuilt the unmodified twin (re-transferred and rebuilt before the decisive run).
Verified recommended fix
In amdgpu_cgs_get_firmware_info (amdgpu_cgs.c:461-472), reject the SMU firmware header unless ucode_array_offset_bytes <= fw->datasize and ucode_size_bytes <= fw->datasize - ucode_array_offset_bytes, before building info->kptr/info->image_size (fix.diff).
Verdict
Harness-verified on the DragonFly 6.5-DEVELOPMENT guest using the verbatim smu7_smumgr.c:461-500 upload functions with 1:1 struct layouts and the verbatim amdgpu_cgs.c:437-472 SMU-branch pointer construction (no AMD GPU on the audit guest; methodology identical to DF-4665/DF-3683/DF-2096): the ONLY checks on the path are amdgpu_ucode_validate's self-declared datasize==size_bytes and the SMC-SRAM length gate limit>=byte_count (smu7_smumgr.c:465). With a crafted SMC firmware header (ucode_array_offset_bytes = end of blob, ucode_size_bytes = 0x1000) the kernel streams 1024/1024 adjacent-heap canary dwords into SMC SRAM via cgs_write_register(mmSMC_IND_DATA_11, *src++); with a wild base (0x40000000 or 0xFFFFF000, size up to the 0x20000 the gate permits) the read faults (panic class). Fix.diff bound check at the construction rejects all three attack headers before any read while the legit header still uploads; 3/3 deterministic runs.
No comments yet.