rv_init_cp_jump_table: unbounded jt_size OOB write + NULL deref on missing mec2 firmware
Summary
rv_init_cp_jump_table at :1078-1127: jt_size from CE/PFP/ME/MEC/MEC2 firmware headers used without bounds validation against cp_table BO (16896 dwords) or fw->datasize. Large jt_size -> OOB write past BO mapping + OOB read past firmware. Also me==4 dereferences adev->gfx.mec2_fw->data without NULL check; mec2_fw is NULL when optional firmware absent (init_microcode :742-745 sets NULL on load failure) -> immediate kernel panic on any Raven APU with missing mec2 firmware. Same class as DF-1134 (gfx_v7) and DF-1164 (gfx_v8). Fix: validate jt_size bounds + NULL-check mec2_fw.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1177 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace reconstruction of rv_init_cp_jump_table loop with oversized jt_size + NULL mec2_fw | 6.5 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 152 B | view raw |
| run.sh | run-script | ./harness | 71 B | view raw |
| build.log | build-log | successful in-guest build | 86 B | view raw |
| run.log | run-log | harness output: OOB write SIGSEGV + NULL deref SIGSEGV | 631 B | view raw |
| env.txt | environment | uname, amdgpu availability, module-build validation, cp_table size | 898 B | view raw |
| fix.diff | suggested-fix | skip me==4 when mec2_fw==NULL + validate jt_size/jt_offset before copy loop | 2.0 KB | view raw |
| VERDICT.md | verdict | full narrative + module-build validation + hard-blocker rationale | 5.3 KB | β raw |
| README.md | readme | human-facing summary | 3.8 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-1177 β rv_init_cp_jump_table jt_size OOB + mec2_fw NULL deref
Finding
rv_init_cp_jump_table() at sys/dev/drm/amd/amdgpu/gfx_v9_0.c:1068-1128
copies jump-table entries from up to five firmware blobs (CE, PFP, ME, MEC,
MEC2) into the cp_table BO. The loop is:
/* gfx_v9_0.c:1077 */ dst_ptr = adev->gfx.rlc.cp_table_ptr;
/* gfx_v9_0.c:1078 */ for (me = 0; me < max_me /* 5 */; me++) {
... fw_data = <firmware ucode_array_offset_bytes>
table_offset = le32_to_cpu(hdr->jt_offset);
table_size = le32_to_cpu(hdr->jt_size);
/* gfx_v9_0.c:1121 */ for (i = 0; i < table_size; i++) {
/* gfx_v9_0.c:1122 */ dst_ptr[bo_offset + i] =
/* gfx_v9_0.c:1123 */ le32_to_cpu(fw_data[table_offset + i]);
}
bo_offset += table_size;
}
Bug 1 β jt_size unbounded β OOB write past cp_table BO
The cp_table BO is allocated at gfx_v9_0.c:1178-1183 with size
ALIGN(96 * 5 * 4, 2048) + (64 * 1024) = 2048 + 65536 = 67584 bytes =
16896 u32 dwords. jt_size (a u32 from each firmware header,
amdgpu_ucode.h:83) is used as the inner loop bound with no validation
against the remaining BO capacity. A large jt_size writes past the BO
mapping β OOB write into adjacent VRAM (corrupts whatever follows).
table_offset + i is likewise unvalidated against fw->datasize β OOB
read past the firmware buffer.
Bug 2 β me == 4 dereferences mec2_fw->data unconditionally
The me == 4 branch at line 1111-1118 reads adev->gfx.mec2_fw->data with
no NULL check. mec2_fw is set to NULL at gfx_v9_0.c:744 when
request_firmware for the (optional) mec2 firmware fails:
/* gfx_v9_0.c:730-745 */
err = request_firmware(&adev->gfx.mec2_fw, fw_name, adev->dev);
if (!err) {
err = amdgpu_ucode_validate(adev->gfx.mec2_fw);
...
} else {
err = 0;
adev->gfx.mec2_fw = NULL; /* <-- optional firmware absent */
}
rv_init_cp_jump_table runs unconditionally for max_me = 5, so a Raven APU
with missing optional mec2 firmware will dereference NULL at line 1113 β
immediate kernel panic.
Same class as DF-1134 (gfx_v7) and DF-1164 (gfx_v8).
Reproducibility on the audit guest
amdgpu is optional (sys/conf/files:2492+); not in X86_64_GENERIC;
QEMU guest has no AMD GPU. The amdgpu.ko module cannot initialize without
AMD hardware, so a live trigger is not possible on this guest.
A userspace harness (harness.c) reconstructs the loop with the 16896-dword
cp_table and demonstrates both bugs:
=== Case 1: large jt_size in CE firmware -> OOB write past cp_table === CE jt_size = 25088 (cp_table = 16896 dwords) -> overflows by 8192 dwords BUG: write past cp_table[CP_TABLE_DWORDS] detected -- kernel equivalent: OOB write past cp_table BO === Case 2: me==4 dereferences adev->gfx.mec2_fw->data with mec2_fw==NULL === (mimics Raven APU boot with missing optional mec2 firmware) BUG: NULL deref of mec2_fw (signal 11) -- kernel equivalent: panic in rv_init_cp_jump_table at gfx_v9_0.c:1113
Threat model
Same as DF-1176: triggerable by root (planting crafted firmware) or a malicious hypervisor. The NULL-deref variant (Bug 2) is also triggerable on any Raven APU system where the optional mec2 firmware file is absent at boot β that is a realistic operational condition, not an attack.
Severity Medium: OOB write requires privileged firmware placement; NULL deref is a real DoS on misconfigured Raven systems.
Recommended fix
- Validate
jt_sizeagainst the remaining cp_table capacity (CP_TABLE_DWORDS - bo_offset) and againstfw->datasizebefore each copy loop. - Skip
me == 4whenmec2_fw == NULL.
See fix.diff.
DF-1177 β VERDICT
Verdict: REPRODUCED (source+harness, module-build-validated). Not uid=0
on the audit guest β this is a memory-corruption primitive (OOB write into
VRAM BO) plus a NULL-deref DoS, but the realistic trigger paths require root,
a malicious hypervisor, OR (for the NULL-deref) a Raven APU with missing
optional mec2 firmware. The OOB-write privilege boundary is absent on this
guest; the NULL-deref is a real DoS on misconfigured hardware.
Bug confirmation
rv_init_cp_jump_table() at sys/dev/drm/amd/amdgpu/gfx_v9_0.c:1068-1128
iterates me = 0..4 over five firmware blobs (CE/PFP/ME/MEC/MEC2) and copies
their jump tables into the cp_table BO:
/* :1077 */ dst_ptr = adev->gfx.rlc.cp_table_ptr;
/* :1078 */ for (me = 0; me < 5; me++) {
... table_size = le32_to_cpu(hdr->jt_size); /* u32 from disk */
/* :1121 */ for (i = 0; i < table_size; i++) {
/* :1122 */ dst_ptr[bo_offset + i] =
/* :1123 */ le32_to_cpu(fw_data[table_offset + i]);
}
bo_offset += table_size;
}
Bug 1 β jt_size unbounded β OOB write past cp_table BO + OOB read past firmware
The cp_table BO is allocated at :1178-1183 with size
ALIGN(96*5*4, 2048) + (64*1024) = 67584 bytes = 16896 u32 dwords.
jt_size (amdgpu_ucode.h:83, uint32_t from each firmware header) is used
as the inner loop bound with no validation against the remaining BO capacity
(16896 - bo_offset), and jt_offset + jt_size is not validated against
fw->datasize. A large jt_size therefore writes past the BO mapping into
adjacent VRAM and reads past the firmware buffer.
Bug 2 β me == 4 dereferences mec2_fw->data unconditionally β NULL deref
The me == 4 branch at :1111-1118 reads adev->gfx.mec2_fw->data with no
NULL check. mec2_fw is set to NULL at :744 when request_firmware for the
optional mec2 firmware fails:
/* :730-745 */
err = request_firmware(&adev->gfx.mec2_fw, fw_name, adev->dev);
if (!err) { ... } else {
err = 0;
adev->gfx.mec2_fw = NULL; /* optional firmware absent */
}
rv_init_cp_jump_table runs unconditionally with max_me = 5, so any Raven
APU (asic_type == CHIP_RAVEN, gated at :1176) with missing optional mec2
firmware dereferences NULL at :1113 β immediate kernel panic.
Reproducibility on the audit guest
amdgpu is optional (sys/conf/files:2492+); not in X86_64_GENERIC;
QEMU guest has no AMD GPU. The amdgpu.ko module cannot initialize without
AMD hardware, so a live trigger is not possible on this guest.
A userspace harness (harness.c) reconstructs the 16896-dword cp_table loop
and demonstrates both bugs:
=== Case 1: large jt_size in CE firmware -> OOB write past cp_table === CE jt_size = 25088 (cp_table = 16896 dwords) -> overflows by 8192 dwords BUG: OOB write faulted (signal 11) -- kernel equivalent: page fault past cp_table BO mapping === Case 2: me==4 dereferences mec2_fw->data with mec2_fw==NULL === BUG: NULL deref of mec2_fw (signal 11) -- kernel equivalent: panic at gfx_v9_0.c:1113
Module build validation
The fix.diff was applied in-guest to /usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.c
and make compiled the patched gfx_v9_0.c cleanly
(/usr/obj/usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.o produced, no warnings,
-Werror). The full amdgpu.ko link step fails on an unrelated pre-existing
issue (color_gamma.o: file is empty); not related to our patch. Source was
reverted after validation.
Exploit chain
blocked by valid hard blocker (root-only reachability for the OOB write;
hardware/operations-config for the NULL deref):
-
OOB write (Bug 1): triggerable only via crafted firmware. Firmware lives in root-owned
/lib/firmware/and is loaded by the kernel only at GPU probe time, which itself requires AMD hardware absent on the QEMU guest. Realistic trigger paths: (a) root β kernel (no boundary to cross), (b) malicious hypervisor presenting forged firmware, (c) misconfigured system with world-writable/lib/firmware. No unprivileged local path on the audit guest. -
NULL deref (Bug 2): triggerable on any Raven APU system where the optional mec2 firmware file is absent at boot. This is an operational misconfiguration DoS rather than an attack, but it is real and would panic the kernel on every boot of such a system.
Concrete next iteration if an unprivileged firmware-write path existed: the OOB write lands in the cp_table VRAM BO. The destination is GPU VRAM, not host kernel heap, so a uid=0 escalation would require a separate primitive that turns a VRAM corruption into a host-kernel corruption (out of scope for a direct chain). For the NULL deref there is no chain β it is a one-shot DoS.
Fix
fix.diff:
1. Skips me == 4 when mec2_fw == NULL (prevents Bug 2).
2. Adds an else { continue; } to the me-switch for safety.
3. Before each copy loop, validates jt_size <= 16896 - bo_offset and
jt_offset + jt_size <= fw->datasize/4 (prevents Bug 1).
git apply --check passes; patched source compiles cleanly into
gfx_v9_0.o on the guest.
Threat model
Same class as DF-1134 (gfx_v7) and DF-1164 (gfx_v8). Severity Medium: OOB write requires privileged firmware placement; NULL deref is a real DoS on misconfigured Raven APU systems.
Fix verification
not_testablecompile+harness validated
module/object build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. gfx_v9_0 rv_init_cp_jump_table jt_size unbounded + mec2_fw NULL. amdgpu not in GENERIC.
No comments yet.