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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1134 Β· 12 files
FileTypeDescriptionSize
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
README.md readme summary + repro
↓ download 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_size accumulates across 4..5 ME blobs.
  • gfx_v8_0.c:1407 β€” cp_table_size = ALIGN(96*5*4, 2048) + 64*1024 = 67584 => capacity 67584/4 = 16896 dwords. dst_ptr is 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 checks datasize==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).

VERDICT.md verdict full narrative
↓ download raw

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 >= 16896 writes past the cp_table BO (corrupts adjacent VRAM BOs / faults).
  • OOB read (CWE-125): any table_offset + i >= fw->datasize/4 reads 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 unprivileged maxx) 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_testable

compile+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.