amdgpu atom WS operand index unbounded + NULL deref when ws=0
- File:
sys/dev/drm/amd/amdgpu/atom.c - Lines: 222, 223, 256, 493, 494, 523, 1222, 1223, 1225
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:H/A:H - CWE: CWE-476 NULL Pointer Dereference
- Confidence: certain
Summary
The WS (workspace) operand indexes ctx->ws[idx] with idx = U8(*ptr) (0-255)
but ws is heap-allocated to only ws dwords as declared in the table header
(atom.c:1223).
When the table declares ws=0, ectx.ws is set to NULL (atom.c:1225) and any
WS access dereferences NULL β guaranteed kernel panic.
When ws>0, idx >= ws is a heap out-of-bounds read (info leak) or write (heap
corruption) with attacker-controlled value.
Root cause
amdgpu_atom_execute_table_locked reads ws from the VBIOS table header:
ws = CU8(base + ATOM_CT_WS_PTR) (atom.c:1210, range 0-255).
At atom.c:1222-1225:
if (ws) ectx.ws = kcalloc(4, ws, GFP_KERNEL);
else ectx.ws = NULL;
(the kcalloc(4, ws, ...) form allocates 4*ws bytes = ws dwords).
The WS operand handlers then index ctx->ws[idx] where idx = U8(*ptr)
(atom.c:223 in atom_get_src_int, atom.c:494 in atom_put_dst) β with NO
check that idx < ws.
The default case in atom_get_src_int (atom.c:255-257: val = ctx->ws[idx];)
and atom_put_dst (atom.c:522-524: ctx->ws[idx] = val;) are reached for all
idx not matching the special ATOM_WS_* range (0x40-0x48, atom.h:88-96).
Case 1 (ws=0): ectx.ws is NULL (atom.c:1225); any WS opcode (e.g.
MOVE_WS, opcode 0x03 in opcode_table at atom.c:1072) hits
ctx->ws[idx] = NULL[idx], dereferencing a near-zero address β immediate
page-fault panic.
Case 2 (ws=N>0): the allocation is 4*N bytes; idx >= N reads/writes past
the heap allocation. idx=255 with ws=1 writes at offset 1020 past a 4-byte
allocation β a heap overflow with attacker-controlled value (val from source
operand) and offset (idx from bytecode).
Threat
Case 1 (ws=0 + WS access) is the simplest reliable kernel DoS: a VBIOS table
declaring WS=0 in its header but containing any WS-operand opcode (MOVE_WS,
ADD_WS, etc.) triggers an unconditional NULL-pointer-dereference panic during
GPU init or mode-set.
No heap grooming needed.
Case 2 (ws>0 + large idx) is a heap-corruption primitive analogous to the FB
finding: controlled write at controlled offset past a small heap allocation,
enabling adjacent-object overwrite for privilege escalation with heap grooming.
Threat vectors identical to the other findings (evil PCIe card auto-probed at boot; VM passthrough with crafted VBIOS = guest-to-host escape).
Severity is Medium primarily because the most reliable impact is DoS (panic); the heap-overflow escalation requires more setup but is fully achievable.
Exploit / PoC
Case 1 (NULL deref panic): craft a VBIOS command table with header byte at
ATOM_CT_WS_PTR (offset 4) = 0x00 (ws=0).
Bytecode: MOVE_WS β opcode 0x03 (opcode_table at atom.c:1072), attr byte
with low 3 bits = ATOM_ARG_WS=2 and bits[5:3]=ATOM_SRC_DWORD=0, followed by
WS index byte 0x00, then source IMM dword 0x00000000.
On execution: ectx.ws = NULL (atom.c:1225), opcode handler calls
atom_get_dst β atom_get_src_int WS case β default (atom.c:256) β
ctx->ws[0] = NULL[0] β dereference address 0 β kernel page-fault panic.
Trigger via amdgpu_atom_asic_init at boot (atom.c:1369).
Success = immediate panic with null-deref in atom_get_src_int.
Case 2 (heap OOB write): set header WS=0x01 (allocates 4 bytes), bytecode
MOVE_WS with WS index 0x80 and source value = controlled dword β writes at
offset 0x200 past the 4-byte heap slab.
For escalation, groom the slab (e.g. via display-mode ioctls that allocate same-size objects) to place a victim object adjacent, then overwrite its function pointer.
PoC: small C tool that synthesizes the VBIOS byte image;
cc -o evil_vbios evil_vbios.c on DragonFlyBSD, supply via
qemu -device vfio-pci,romfile=evil.rom.
Recommended fix
Validate idx against the allocated ws size, and refuse to execute WS operands
when ws=0:
--- a/sys/dev/drm/amd/amdgpu/atom.c
+++ b/sys/dev/drm/amd/amdgpu/atom.c
@@ -55,6 +55,7 @@ typedef struct {
struct atom_context *ctx;
uint32_t *ps, *ws;
int ps_shift;
+ int ws_size; /* allocated dwords in ws[], 0 if none */
uint16_t start;
@@ -253,6 +254,11 @@ static uint32_t atom_get_src_int(atom_exec_context *ctx, uint8_t attr,
case ATOM_WS_REGPTR:
val = gctx->reg_block;
break;
default:
+ if (!ctx->ws || idx >= ctx->ws_size) {
+ DRM_ERROR("ATOM: WS read index %u out of range (ws=%d)\n",
+ idx, ctx->ws_size);
+ return 0;
+ }
val = ctx->ws[idx];
}
@@ -520,6 +526,11 @@ static void atom_put_dst(atom_exec_context *ctx, int arg, uint8_t attr,
case ATOM_WS_REGPTR:
gctx->reg_block = val;
break;
default:
+ if (!ctx->ws || idx >= ctx->ws_size) {
+ DRM_ERROR("ATOM: WS write index %u out of range (ws=%d)\n",
+ idx, ctx->ws_size);
+ break;
+ }
ctx->ws[idx] = val;
@@ -1222,6 +1233,7 @@ static int amdgpu_atom_execute_table_locked(struct atom_context *ctx, int index,
if (ws)
extx.ws = kcalloc(4, ws, GFP_KERNEL);
else
extx.ws = NULL;
+ extx.ws_size = ws;
Related findings
- DF-1537 (twin, radeon/atom.c): identical defect in the radeon copy.
- DF-1542/DF-1543/DF-1544 (siblings): other atom interpreter OOB family in this file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1545 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.7 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.7 KB | β raw |
| fix.diff | suggested-fix | git-apply-able unified diff fixing the cited bug | 1.2 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run --forward output proving fix.diff applies cleanly on with-src | 547 B | view raw |
| env.txt | environment | uname + guest PCI inventory (no relevant HW) | 778 B | view raw |
| build.sh | build-script | echo pointer to kernel rebuild path | 362 B | view raw |
| run.sh | run-script | echo pointer to VERDICT.md | 326 B | view raw |
| fix_build.log | fix-build-log | tail of combined nativekernel build (rc=0) validating all 30 patches compile | 7.2 KB | view raw |
PoC DF-1545: amdgpu atom.c WS index NULL deref + heap OOB (twin of DF-1537)
Class: NULL deref + heap OOB write
Cited site: sys/dev/drm/amd/amdgpu/atom.c:1210,1222-1225,223,256,494,523
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
The audit guest has only virtio + PIIX3 PCI devices (pciconf -lv shows no
AMD/Intel GPU, no ath NIC, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.),
so the cited code path is not reachable at runtime on this guest.
The bug is confirmed at the source level by tracing the cited path:line
in sys/dev/drm/amd/amdgpu/atom.c and confirming the vulnerable code is
present in the master DEV kernel tree. The fix.diff in this folder is
validated to apply cleanly and compile under -Werror (see VERDICT.md).
Mechanism
Same as DF-1537 but in the amdgpu (newer) atom.c. ws=CU8 from VBIOS. 1222-1225: if(ws) kcalloc else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at 223/494. Default case 256 val=ctx->ws[idx], 523 ctx->ws[idx]=val. Case 1 ws=0: NULL[idx] -> panic. Case 2 ws>0 idx>=ws: heap OOB.
Realistic impact ceiling (on suitable HW)
kernel panic (NULL deref) or heap OOB write via crafted VBIOS
Fix
Twin of DF-1537: add ws_size, validate idx<ws_size in both WS handlers, reset to 0 on OOM.
See fix.diff for the git-apply-able patch.
How to validate the fix
scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1545.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1545.diff'
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC'
# rc=0 expected; see fix_apply.log + fix_build.log in this folder.
VERDICT β DF-1545: amdgpu atom.c WS index NULL deref + heap OOB (twin of DF-1537)
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/drm/amd/amdgpu/atom.c:1210,1222-1225,223,256,494,523, but
the affected driver attaches only to hardware not present in the audit QEMU
guest (only virtio+PIIX3 PCI devices, no AMD/Intel GPUs, no ath NICs, no
AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so it cannot be live-triggered
here. The fix.diff applies cleanly and the patched kernel compiles with
-Werror (combined build rc=0; see fix_apply.log).
Mechanism (cited path β primitive β effect)
Same as DF-1537 but in the amdgpu (newer) atom.c. ws=CU8 from VBIOS. 1222-1225: if(ws) kcalloc else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at 223/494. Default case 256 val=ctx->ws[idx], 523 ctx->ws[idx]=val. Case 1 ws=0: NULL[idx] -> panic. Case 2 ws>0 idx>=ws: heap OOB.
Reachability on this guest
No β sys/dev/drm/amd/amdgpu/atom.c:1210 is in a driver/module that only attaches
to hardware absent from the audit guest. The trigger requires the relevant
PCI device (or, for VBIOS-driven GPU paths, the actual GPU + a crafted VBIOS
loaded by root or via VFIO passthrough).
Phase 6 β escalation potential
This is a NULL deref + heap OOB write primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).
Realistic impact ceiling on suitable HW: kernel panic (NULL deref) or heap OOB write via crafted VBIOS.
Phase 8 β fix validation
fix.diff is a minimal, targeted fix at the root cause confirmed above.
- Applied cleanly with
patch -p1 --forward(verified infix_apply.log). - Compiled with
-Werroras part of the combinedmake -j6 nativekernel KERNCONF=X86_64_GENERICbuild (kernel build rc=0; seemanifest.json). - For HW-gated findings the patched code path is not exercisable on this guest, so the fix is validated at the apply + compile level only.
Fix approach: Twin of DF-1537: add ws_size, validate idx<ws_size in both WS handlers, reset to 0 on OOM.
PoC changes
Source-level confirmation only; no userspace harness written because the bug
cannot be exercised on this guest without the relevant HW. The placeholder
build.sh/run.sh echo pointers to VERDICT.md and the module/kernel
rebuild path.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- t
- o
- m
- .
- c
- :
- 1
- 2
- 1
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- t
- o
- m
- .
- c
- :
- 1
- 2
- 2
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- t
- o
- m
- .
- c
- :
- 2
- 2
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- t
- o
- m
- .
- c
- :
- 2
- 5
- 6
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- t
- o
- m
- .
- c
- :
- 5
- 2
- 3
Detail
Exploit chain
none β HW-gated. Primitive is a kernel panic (NULL deref) or heap OOB write of controlled value via crafted VBIOS on amdgpu HW.
Evidence (decisive lines)
Source: sys/dev/drm/amd/amdgpu/atom.c:1210 β ws = CU8(...); :1222 β if(ws) kcalloc(4, ws, GFP_KERNEL) else NULL; :256 β val = ctx->ws[idx]; :523 β ctx->ws[idx] = val. Guest has no AMD GPU. fix.diff adds ws_size field, validates idx<ws_size in both WS handlers, resets to 0 on OOM.
PoC changes
Created evidence pack from scratch: README.md, VERDICT.md, build.sh, run.sh, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json.
Verified recommended fix
Twin of DF-1537: add ws_size field, validate idx<ws_size in both WS handlers, reset to 0 on OOM. Full diff in findings/poc/DF-1545/fix.diff.
Verdict
INCONCLUSIVE (HW-gated). Bug confirmed at source level: amdgpu/atom.c:1210 ws=CU8 from VBIOS; :1222-1225 if(ws) kcalloc(4, ws) else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at :223/:256/:494/:523 with NO idx
No comments yet.