amdgpu_cgs_get_firmware_info indexes adev->firmware.ucode[fw_type_convert(type)] with no bound on the converter's default return (== the array size): one-past-end element whose .fw aliases firmware.fw_buf β type-confused deref of amdgpu_bo internals as struct firmware (latent: no in-tree caller passes an unmapped type)
Summary
The converter's default return is exactly AMDGPU_UCODE_ID_MAXIMUM == the array size, and the enum already ships an unhandled value - a future/garbage type reads ucode[MAXIMUM].fw which lands exactly on firmware.fw_buf (alias _Static_assert-proven and runtime-confirmed), bypasses the NULL guard, and reinterprets the amdgpu_bo as struct firmware (wild read -> info fields). Latent (all in-tree callers pass mapped types). Demonstrated in the DF-4862 harness scenario 4 (returns 0 with garbage image_size); patched twin rejects. Range-check-the-converter fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4863 Β· 13 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 | β | 100 B | view raw | |
| build.log | β | 447 B | view raw | |
| run.log | β | 3.5 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 | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-4863 β adev->firmware.ucode[fw_type_convert(...)] one-past-end index on the default path
What
amdgpu_cgs_get_firmware_info() non-SMU branch
(sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:214-215):
id = fw_type_convert(cgs_device, type);
ucode = &adev->firmware.ucode[id];
if (ucode->fw == NULL) /* <-- OOB read here */
return -EINVAL;
fw_type_convert() (amdgpu_cgs.c:111-156) returns
AMDGPU_UCODE_ID_MAXIMUM (= 21) from its default case β exactly the
declared size of adev->firmware.ucode[] (amdgpu_ucode.h:252-253), and
the return value is never range-checked before indexing. Any CGS ucode id
that fw_type_convert does not map β today that includes
CGS_UCODE_ID_GMCON_RENG (cgs_common.h:58, which exists in the enum but
is unhandled), CGS_UCODE_ID_MAXIMUM itself, or any future/garbage value β
produces a one-past-the-end element:
ucode[MAXIMUM].fw(element offset +8) reads exactlyadev->firmware.fw_buf(thestruct amdgpu_bo *at struct offset 1016 β_Static_asserted in the harness, and proven at runtime:&ucode[MAXIMUM].fw == &firmware.fw_buf).- On SMU/PSP-load asics
fw_bufis non-NULL afteramdgpu_ucode_init_bo()(whichsmu7_request_smu_load_fw()invokes first thing,smu7_smumgr.c:343), so the NULL guard passes andheader = (gfx_firmware_header_v1_0 *)ucode->fw->datatreatsstruct amdgpu_bointernals asstruct firmware(sys/sys/firmware.h:51,dataat offset 8) and dereferences the result β a type-confused wild read (panic class), with garbage flowing intoinfo->image_size/info->version/info->kptrfor downstream SMU consumers.
Reachability today: latent β every in-tree caller
(smu7/smu8/ci/iceland/vega10/vega12/vega20 smumgrs) passes only types that
fw_type_convert maps. The defect is the missing bound on a converter whose
default return is precisely the array size β a landmine for the next caller
(the enum already contains the unhandled GMCON_RENG value). Filed as
Low/hardening; the alias itself is proven at runtime.
Build / run / expected
Same harness as DF-4862 (sh build.sh && sh run.sh); this finding's
trigger is scenario [4]. Expected baseline:
[4] fw_type_convert default -> AMDGPU_UCODE_ID_MAXIMUM one-past-end &firmware.ucode[MAXIMUM].fw = 0x403758 == &firmware.firmware... : ALIAS CONFIRMED cgs_get_firmware_info(CGS_UCODE_ID_GMCON_RENG) = 0 type-confused header=bo+8 -> image_size=0x5a5a5a5a ... propagated to caller
Patched twin (index guard from fix.diff):
cgs_get_firmware_info(CGS_UCODE_ID_GMCON_RENG) = -22 (one-past-end index rejected β FIX VALIDATED)
The harness sets firmware.fw_buf to a mapped mock whose +8 qword points
into a readable page, so the verbatim code runs to completion and the
garbage propagation is shown instead of crashing; on real hardware that
qword is struct amdgpu_bo/ttm_buffer_object internals β an arbitrary
pointer.
DF-4863 β VERDICT
Status: reproduced (defect demonstrated in the verbatim-function harness, 3/3 deterministic runs) Impact: none (latent panic-class type confusion β no in-tree caller reaches the default path today) Severity filed: Low Β· confidence: certain (code defect) / reachability latent (documented)
What was run
The DF-4862 harness (cgs_mec_jt_oob.c), scenario [4], on the DragonFly
6.5-DEVELOPMENT guest. Struct layouts are 1:1 with
amdgpu_ucode.h:236-268 and enforced at compile time:
_Static_assert(AMDGPU_UCODE_ID_MAXIMUM == 21)_Static_assert(offsetof(struct amdgpu_firmware, fw_buf) == AMDGPU_UCODE_ID_MAXIMUM * 48 + 8)β i.e.ucode[21].fw(element offset +8 of the one-past-end element) lands exactly onfw_buf.
Observed (run.log, scenario [4])
&firmware.ucode[MAXIMUM].fw = 0x403758 == &firmware.fw_buf = 0x403758 : ALIAS CONFIRMEDβ the one-past-end read is proven to fetch thefw_bufpointer.- With
fw_bufnon-NULL (its state afteramdgpu_ucode_init_bo()on SMU-load asics βsmu7_request_smu_load_fw()calls it first thing atsmu7_smumgr.c:343), the verbatimucode->fw == NULLguard passes and the code proceeds:header = ucode->fw->datareads the mock bo's +8 qword asconst uint8_t *data(sys/sys/firmware.h:53), andinfo.image_size=0x5a5a5a5a/info.version=0x5a5agarbage propagates to the caller. The harness points that qword at a readable page so the run completes; on real hardware it isttm_buffer_objectinternals β an arbitrary pointer, andle32_to_cpu(header->header.ucode_size_bytes)(amdgpu_cgs.c:221) dereferences it immediately: wild read, panic class.
Root cause
sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:214-215 β the return of
fw_type_convert() (:111-156) is used as an array index without a bound;
its default return (AMDGPU_UCODE_ID_MAXIMUM, :114) equals
ARRAY_SIZE(adev->firmware.ucode) (amdgpu_ucode.h:252-253). The enum
cgs_ucode_id already contains an unhandled value
(CGS_UCODE_ID_GMCON_RENG, cgs_common.h:58).
Reachability (why Low, not higher)
All in-tree cgs_get_firmware_info callers pass mapped types:
smu7 (smu7_smumgr.c:312,489,492 via smu7_convert_fw_type_to_cgs β all
cases mapped, smu7_smumgr.c:231-276), smu8 (smu8_smumgr.c:189,569 via
smu8_convert_fw_type_to_cgs β all firmware_list[] entries mapped),
ci (ci_smumgr.c:2316 SMU), iceland (:192 SMU),
vega10/12/20 (SMU). So today the path is dead code for unmapped types; the
defect is a missing defensive bound whose trigger is one future caller (or
one enum value gaining a user) away.
Fix validation
fix.diff (same file as DF-4862's pack) adds
if (id >= AMDGPU_UCODE_ID_MAXIMUM) return -EINVAL; before the indexing.
Patched twin: scenario [4] returns -22 (one-past-end index rejected β FIX
VALIDATED); no other scenario changes. 3/3 runs.
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 adds 'if (id >= AMDGPU_UCODE_ID_MAXIMUM) return -EINVAL;' before the ucode[] indexing (fix.diff, same patch as DF-4862): the one-past-end call returns -22 before any OOB element read, while all mapped-type scenarios behave identically. Full kernel rebuild not performed (no AMD GPU on the audit guest; verbatim-function harness is the faithful surface).
findings/poc/DF-4863/run.log patched-twin scenario [4]: 'cgs_get_firmware_info(CGS_UCODE_ID_GMCON_RENG) = -22 (one-past-end index rejected - FIX VALIDATED)'.
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4863/run.log scenario [4]: '&firmware.ucode[MAXIMUM].fw = 0x403758 == &firmware.fw_buf = 0x403758 : ALIAS CONFIRMED'; baseline 'cgs_get_firmware_info(CGS_UCODE_ID_GMCON_RENG) = 0' with 'type-confused header=bo+8 -> image_size=0x5a5a5a5a version=0x5a5a propagated to caller'; patched twin '-22 (one-past-end index rejected - FIX VALIDATED)'. Compile-time proof in cgs_mec_jt_oob.c (_Static_assert(offsetof(amdgpu_firmware, fw_buf) == MAXIMUM*48+8)).
PoC changes
No separate PoC authored: the defect is demonstrated by scenario [4] of the DF-4862 harness built in the same run (same verbatim cgs function, same struct replicas); this pack carries the same sources and the scenario-[4]-focused logs/verdicts.
Verified recommended fix
Range-check fw_type_convert()'s return before indexing: if (id >= AMDGPU_UCODE_ID_MAXIMUM) return -EINVAL; at amdgpu_cgs.c:214 (fix.diff).
Verdict
Harness-verified on the DragonFly 6.5-DEVELOPMENT guest (scenario [4] of the DF-4862 verbatim-function harness, 1:1 struct layouts enforced by _Static_assert): amdgpu_cgs.c:214-215 indexes adev->firmware.ucode[] with fw_type_convert()'s return without any bound check, and that helper's DEFAULT return is AMDGPU_UCODE_ID_MAXIMUM == 21 == the array size. Runtime proof: &ucode[MAXIMUM].fw == &firmware.fw_buf (ALIAS CONFIRMED) - the OOB read fetches the amdgpu_bo* fw_buf field; with fw_buf non-NULL (its state after amdgpu_ucode_init_bo, which smu7_request_smu_load_fw calls first) the NULL guard passes and the verbatim code reinterprets amdgpu_bo internals as struct firmware, dereferencing ->data (sys/sys/firmware.h:53) and propagating garbage (image_size=0x5a5a5a5a) to the caller - on real hardware a wild read, panic class. Reachability is LATENT: every in-tree caller (smu7/smu8/ci/iceland/vega10/12/20 smumgrs) passes only mapped types today; the enum already ships an unmapped value (CGS_UCODE_ID_GMCON_RENG). Patched twin rejects the one-past-end index with -EINVAL (FIX VALIDATED); 3/3 deterministic runs.
No comments yet.