β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4774

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4774 Β· 12 files
FileTypeDescriptionSize
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) rejects size > 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.

VERDICT.md
↓ download raw

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)

  1. 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 via cgs_write_register(mmSMC_IND_DATA_11, *src++) (smu7_smumgr.c:470-471).
  2. Wild-read panic class. ucode_array_offset_bytes = 0x40000000 (1 GB past) with ucode_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.
  3. The only check is the length gate. limit >= byte_count (smu7_smumgr.c:465, limit = SMU7_SMC_SIZE = 0x20000) rejects ucode_size_bytes > 0x20000 but never examines the base offset, and nothing on the path compares ucode_array_offset_bytes or ucode_size_bytes against fw->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 β€” sources
  • build.sh, run.sh, build.log, run.log, run.2.log, run.3.log, env.txt
  • fix.diff β€” bound check at amdgpu_cgs.c:461-472 (never applied to sys/)

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched-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).
↓ fix.diffper-fix-DF-4774

Confirmed kernel references

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.