cz_init_cp_jump_table: unvalidated jt_offset/jt_size from CE/PFP/ME/MEC fw headers -> OOB read/write
Summary
cz_init_cp_jump_table at gfx_v8_0.c:1301-1364 for Carrizo/Stoney: reads jt_offset/jt_size from CE/PFP/ME/MEC firmware headers without bounds validation. dst_ptr[bo_offset+i]=le32_to_cpu(fw_data[table_offset+i]) at :1357-1359. cp_table BO is 67584 bytes. Any jt_size exceeding capacity OOB-writes past BO mapping; any table_offset exceeding fw->datasize/4 OOB-reads past firmware blob. amdgpu_ucode_validate only checks datasize==size_bytes. Same class as DF-1119/DF-1120. Fix: validate ucode_array_offset+(jt_offset+jt_size)*4<=datasize and bo_offset+jt_size<=cp_table_size/4.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1134 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replica of cz_init_cp_jump_table jt bounds + WITH-FIX pass | 4.5 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 114 B | view raw |
| run.sh | run-script | ./harness | 60 B | view raw |
| build.log | build-log | final build, full output | 8 B | view raw |
| run.log | run-log | decisive run incl CONFIRMED + FIX VALIDATED | 1.1 KB | view raw |
| env.txt | environment | uname, cc, pciconf (no AMD APU) | 241 B | view raw |
| fix.diff | suggested-fix | validate jt_offset/jt_size vs fw blob and cp_table BO in cz_init_cp_jump_table | 2.4 KB | view raw |
| fix_validation.txt | fix-validation | apply-check + compile (gfx_v8_0.o rc=0) + harness fix-demo | 1.7 KB | view raw |
| VERDICT.md | verdict | full narrative | 2.9 KB | β raw |
| README.md | readme | summary + repro | 2.3 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1134 β cz_init_cp_jump_table jt_offset/jt_size OOB read/write
Verdict
CONFIRMED (source-trace + harness) β INCONCLUSIVE on-guest (HW-gated). Real bug; not triggerable on the QEMU guest (no AMD Carrizo/Stoney APU; amdgpu not in GENERIC).
Bug (one line)
cz_init_cp_jump_table() reads jt_offset/jt_size from each CE/PFP/ME/MEC/MEC2
firmware header with no bounds check, then dst_ptr[bo_offset+i] = fw_data[table_offset+i]
for i in [0, jt_size) β a large jt_size writes past the 67584-byte cp_table VRAM
BO and a large jt_offset reads past the firmware blob.
Mechanism (path:line)
gfx_v8_0.c:1321-1322(and 1329/1337/1345/1353) βtable_offset = le32(hdr->jt_offset),table_size = le32(hdr->jt_size), both unvalidated.gfx_v8_0.c:1318-1320βfw_data = fw->data + ucode_array_offset_bytes(offset unchecked).gfx_v8_0.c:1357-1359βdst_ptr[bo_offset+i] = le32(fw_data[table_offset+i]).gfx_v8_0.c:1362βbo_offset += table_sizeaccumulates across 4..5 ME blobs.gfx_v8_0.c:1407βcp_table_size = ALIGN(96*5*4, 2048) + 64*1024 = 67584=> capacity67584/4 = 16896dwords.dst_ptris the VRAM BO mapping.- OOB write:
bo_offset + i >= 16896. OOB read:table_offset + i >= fw->datasize/4. amdgpu_ucode.c:251-260βamdgpu_ucode_validate()only checksdatasize==size_bytes.
Trigger / threat model
Crafted CE/PFP/ME/MEC firmware headers on a Carrizo/Stoney APU (reflash / KVM passthrough / emulated). OOB write corrupts adjacent VRAM BOs; OOB read pulls adjacent kernel heap into VRAM. CVSS PR:H (privileged loader).
Reproduction on the audit guest
Not possible β no AMD APU; amdgpu not in GENERIC. harness.c models the per-ME loop
with attacker-chosen jt_offset/jt_size and shows 4300 OOB-write + 18072 OOB-read
iterations, then the fix (reject invalid jt bounds) yields 0 OOB.
Build / run
./build.sh && ./run.sh
Expected: DF-1134: CONFIRMED OOB write past cp_table VRAM BO AND/OR OOB read past firmware blob
then DF-1134 FIX: VALIDATED - bounds validation rejects all OOB jt_offset/jt_size.
Fix
fix.diff captures fw_dwords per ME branch and, before the copy loop, rejects
jt_size/jt_offset that would read past the firmware blob or write past the
cp_table BO (continue on violation). Applies cleanly; compiles in the amdgpu
module build (gfx_v8_0.o, rc=0).
DF-1134 β VERDICT
Verdict: CONFIRMED via source-trace + userspace harness. On-guest: INCONCLUSIVE (HW-gated β no AMD Carrizo/Stoney APU; amdgpu not in GENERIC, only LINT64).
Root-cause confirmation
cz_init_cp_jump_table() (gfx_v8_0.c:1301-1364) iterates over the CE/PFP/ME/MEC
(and on Carrizo, MEC2) firmware blobs. For each it reads
table_offset = le32(hdr->jt_offset) and table_size = le32(hdr->jt_size) straight
from the firmware header with no validation, sets fw_data from an unchecked
ucode_array_offset_bytes, and runs
for (i=0;i<table_size;i++) dst_ptr[bo_offset+i] = le32(fw_data[table_offset+i]);
(gfx_v8_0.c:1357-1359), with bo_offset += table_size accumulating across all MEs
(:1362). dst_ptr is the cp_table VRAM BO mapping of
cp_table_size = ALIGN(96*5*4,2048) + 64*1024 = 67584 bytes = 16896 dwords
(gfx_v8_0.c:1407).
- OOB write (CWE-787): any
bo_offset + i >= 16896writes past the cp_table BO (corrupts adjacent VRAM BOs / faults). - OOB read (CWE-125): any
table_offset + i >= fw->datasize/4reads past the firmware blob into adjacent kernel heap.
amdgpu_ucode_validate() (amdgpu_ucode.c:251-260) only checks
fw->datasize == hdr->size_bytes and validates neither jt_offset, jt_size, nor the
ucode array offset.
Evidence
harness.c(run as unprivilegedmaxx) models four ME blobs with attacker-chosen jt_offset/jt_size and reports 4300 OOB-write iterations (dst past cp_table) and 18072 OOB-read iterations (src past fw blob); cumulative bo_offset 21196 > 16896.
Exploit chain / impact
Combined write-into-VRAM + read-from-heap primitives. On GENERIC this path is not
compiled in (amdgpu absent). On real Carrizo/Stoney hardware with malicious firmware
headers, the realistic GENERIC outcome is a panic (VRAM OOB fault / corrupted GPU
command state) and/or an info leak via the heap read. No uid=0 chain developed β the
path is unreachable on this guest. Realistic ceiling: panic / info leak.
Fix validation
fix.diff captures fw_dwords per ME branch and, before the copy loop, rejects
(continue) any jt_size/jt_offset that would read past the firmware blob or write
past the cp_table BO.
- git apply --check -p1 => OK (applies alongside DF-1133 to the same file, no conflict).
- Compiles in the amdgpu module: make gfx_v8_0.o built gfx_v8_0.o (116648 B,
-Werror clean) with both DF-1133 and DF-1134 fixes applied; full nativekernel rc=0.
- Harness "WITH FIX" pass: 0 OOB-write / 0 OOB-read iterations remain.
- fix_status: not_testable (HW-gated runtime; apply-check + compile + harness fix-demo + trace all pass).
PoC changes
Evidence pack authored from scratch: harness.c (+ WITH-FIX pass), build.sh,
run.sh, fix.diff, VERDICT.md, manifest.json, logs.
Kernel refs (confirmed during verification)
sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1301, :1321, :1357, :1362, :1407;
sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251.
Fix verification
not_testablecompile+harness validated
module build rc=0 + harness 0 OOB
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed+harness. cz_init_cp_jump_table unvalidated jt_offset/jt_size -> OOB write past cp_table + OOB read past fw. amdgpu not in GENERIC.
No comments yet.