Unbounded WS operand index and NULL dereference when ws=0
- File:
sys/dev/drm/radeon/atom.c - Lines: 233, 234, 266, 267, 507, 508, 536, 537, 1187, 1188, 1189, 1190
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:N/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.
When the table declares ws=0, ectx.ws is set to NULL, 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 an attacker-controlled value.
Root cause
atom_execute_table_locked reads ws from the VBIOS table header:
ws = CU8(base + ATOM_CT_WS_PTR) (atom.c:1175, range 0β255).
At atom.c:1187-1190:
if (ws) ectx.ws = kzalloc(4 * ws, GFP_KERNEL);
else ectx.ws = NULL;
The WS operand handlers then index ctx->ws[idx] where idx = U8(*ptr)
(atom.c:234, range 0β255) β with NO check that idx < ws.
The default case in atom_get_src_int (atom.c:266-268: val = ctx->ws[idx])
and atom_put_dst (atom.c:536-538: ctx->ws[idx] = val) are reached for all
idx not matching the special ATOM_WS_* range (0x40β0x48).
Case 1 β ws=0: ectx.ws is NULL (atom.c:1190); any WS opcode (e.g.
MOVE_WS) 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 and offset (val
from source operand, 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 (e.g.
MOVE_WS, ADD_WS) 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, VM passthrough with crafted VBIOS).
Severity is Medium primarily because the most reliable impact is DoS (panic); the heap-overflow escalation requires more setup.
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 (atom-names.h:38), 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:1190), opcode handler calls
atom_get_dst β atom_get_src_int WS case β default β ctx->ws[0] = NULL[0]
β dereference address 0 β kernel page-fault panic.
Trigger via atom_asic_init at boot.
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.
Recommended fix
Validate idx against the allocated ws size, and refuse to execute WS operands
when ws=0:
--- a/sys/dev/drm/radeon/atom.c
+++ b/sys/dev/drm/radeon/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;
@@ -263,6 +264,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];
}
break;
@@ -534,6 +540,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;
}
--- a/sys/dev/drm/radeon/atom.c
+++ b/sys/dev/drm/radeon/atom.c
@@ -1190,6 +1196,7 @@ static int atom_execute_table_locked(struct atom_context *ctx, int index,
if (ws)
extx.ws = kzalloc(4 * ws, GFP_KERNEL);
else
extx.ws = NULL;
+ extx.ws_size = ws;
Related findings
- DF-1534 (sibling): unbounded recursion in
atom_op_calltable. - DF-1535 (sibling): integer overflow in FB scratch bounds check.
- DF-1536 (sibling): unbounded PS operand index stack OOB.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1537 Β· 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 | 322 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-1537: radeon atom.c WS index NULL deref + heap OOB
Class: NULL deref + heap OOB write
Cited site: sys/dev/drm/radeon/atom.c:1175,1187-1190,234,267,508,537
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/radeon/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
ws=CU8(base+ATOM_CT_WS_PTR) u8 0..255. 1187-1190: if(ws) kzalloc(4*ws) else ectx.ws=NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at 234/267/508/537 with NO idx
Realistic impact ceiling (on suitable HW)
kernel panic (NULL deref) or up to ~1020-byte heap OOB read+write via crafted VBIOS
Fix
Add ws_size field to atom_exec_context; validate idx<ws_size in both WS handlers; reset ws_size 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-1537.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1537.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-1537: radeon atom.c WS index NULL deref + heap OOB
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/drm/radeon/atom.c:1175,1187-1190,234,267,508,537, 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)
ws=CU8(base+ATOM_CT_WS_PTR) u8 0..255. 1187-1190: if(ws) kzalloc(4*ws) else ectx.ws=NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at 234/267/508/537 with NO idx
Reachability on this guest
No β sys/dev/drm/radeon/atom.c:1175 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 up to ~1020-byte heap OOB read+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: Add ws_size field to atom_exec_context; validate idx<ws_size in both WS handlers; reset ws_size 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
- /
- r
- a
- d
- e
- o
- n
- /
- a
- t
- o
- m
- .
- c
- :
- 1
- 1
- 7
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- a
- t
- o
- m
- .
- c
- :
- 1
- 1
- 8
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- a
- t
- o
- m
- .
- c
- :
- 2
- 3
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- a
- t
- o
- m
- .
- c
- :
- 2
- 6
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- a
- t
- o
- m
- .
- c
- :
- 5
- 3
- 7
Detail
Exploit chain
none β HW-gated. Primitive is a kernel panic (NULL deref) or up to ~1020-byte heap OOB read+write via crafted VBIOS on radeon HW.
Evidence (decisive lines)
Source: sys/dev/drm/radeon/atom.c:1175 β ws = CU8(...); :1187 β if(ws) kzalloc(4*ws) else NULL; :267 β val = ctx->ws[idx] (no bounds); :537 β ctx->ws[idx] = val (no bounds). Guest has no AMD GPU. fix.diff adds ws_size field, validates idx<ws_size in both WS handlers, and resets ws_size 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
Add ws_size field to atom_exec_context; set it in atom_execute_table_locked; validate idx<ws_size in both WS handlers (atom_get_src_int and atom_put_dst); reset ws_size to 0 on OOM. Full diff in findings/poc/DF-1537/fix.diff.
Verdict
INCONCLUSIVE (HW-gated). Bug confirmed at source level: radeon/atom.c:1175 ws=CU8(base+ATOM_CT_WS_PTR) u8 (0..255); :1187-1190 if(ws) kzalloc(4*ws) else NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at :234/:267/:508/:537 with NO idx
No comments yet.