Missing size validation for AMDGPU_CHUNK_ID_IB allows OOB read of drm_amdgpu_cs_chunk_ib fields
- File:
sys/dev/drm/amd/amdgpu/amdgpu_cs.c - Lines: 166, 169, 175, 181, 182, 183, 184, 999, 1004, 1017, 1040, 1041
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - CWE: CWE-125 Out-of-bounds Read
- Confidence: likely
Summary
amdgpu_cs_parser_init validates that length_dw is large enough for FENCE
chunks (line 188) and BO_HANDLES chunks (line 202), but the
AMDGPU_CHUNK_ID_IB case at lines 181β184 only increments num_ibs without
any size check. A user can submit an IB chunk with length_dw as small as 1
(4-byte kdata buffer). amdgpu_cs_ib_fill then casts kdata to
(struct drm_amdgpu_cs_chunk_ib *) and reads fields at offsets up to 28 from a
4-byte allocation, causing heap OOB reads of up to 28 bytes from adjacent slab
objects.
Root cause
amdgpu_cs.c:181-184:
case AMDGPU_CHUNK_ID_IB:
++num_ibs;
break;
No length_dw * sizeof(uint32_t) < sizeof(struct drm_amdgpu_cs_chunk_ib)
check, unlike the FENCE (line 188) and BO_HANDLES (line 202) cases.
The chunk's kdata is allocated as
kvmalloc_array(size, sizeof(uint32_t), GFP_KERNEL) at line 169 with
size=length_dw, and length_dw*4 bytes are copied at line 176.
Then amdgpu_cs_ib_fill:999 does
chunk_ib = (struct drm_amdgpu_cs_chunk_ib *)chunk->kdata; and reads
chunk_ib->ip_type (offset 20), ip_instance (offset 24), ring (offset 28),
flags (offset 4), va_start (offset 8), ib_bytes (offset 16) β all OOB
when length_dw < 8. The struct drm_amdgpu_cs_chunk_ib
(uapi: drm_amdgpu_drm.h:562-576) is 32 bytes = 8 dwords.
There is a secondary issue: line 175 size *= sizeof(uint32_t) truncates the
byte count to 32-bit unsigned, so for length_dw = 0x40000000 (if the 4 GB
kvmalloc somehow succeeds) copy_from_user copies 0 bytes, leaving the entire
buffer as uninitialized kvmalloc memory that is then read as chunk_ib
fields.
Threat
Any unprivileged local user via /dev/dri/renderDXX.
Submit amdgpu_cs with one IB chunk whose length_dw is 1..7. The OOB read of
up to 28 bytes of adjacent kernel heap is consumed as IB control fields:
ip_type/ip_instance/ringfeedamdgpu_ctx_get_entity(which validates them β random values are usually rejected with-EINVAL, blocking further progress);ib_bytesdrivesamdgpu_ib_getSA allocation size for parse_cs rings (UVD/VCE) β random huge value typically fails allocation.
No direct path returns the read bytes to userspace, so this is primarily a kernel-hardening defect with possible DoS via huge SA-allocation attempts and theoretical info-leak under slab grooming.
Upstream Linux added this check (modern amdgpu_cs.c has
if (size < sizeof(struct drm_amdgpu_cs_chunk_ib)) return -EINVAL; in the IB
case); DragonFly is missing it.
Exploit / PoC
PoC: open render node, alloc ctx, alloc a 1-dword IB chunk (length_dw=1,
chunk_data=any u32), alloc a deps chunk, submit CS.
Kernel OOB-reads chunk_ib->{flags, va_start, ib_bytes, ip_type, ip_instance, ring}
from the 4-byte kmalloc(4) slab object plus the following 24+ bytes of
adjacent slab.
With KASAN enabled the access is flagged immediately; without KASAN the random
adjacent bytes usually fail ctx_get_entity validation β -EINVAL returned to
user.
To demonstrate heap info disclosure: groom the slab (spray kmalloc-32 objects
containing a marker pattern via amdgpu_bo userptr), submit the tiny IB chunk,
observe whether the CS proceeds past amdgpu_ctx_get_entity (indicates the
leaked ip_type/instance/ring happened to be valid).
The PoC materialises as: compile a libdrm C program that opens renderD128,
calls AMDGPU_CTX_OP_ALLOC_CTX, then DRM_IOCTL_AMDGPU_CS with
chunks=[{IB, length_dw=1, chunk_data=&(u32){0}}].
Build:
cc -ldr -I/usr/local/include/libdrm -o ib_oob_poc ib_oob_poc.c
Success = no -EINVAL before reaching amdgpu_cs_ib_fill's entity check
(proves the OOB read occurred) plus KASAN/UMA zone report showing read past
allocation.
Recommended fix
Add an explicit size check in the IB case, mirroring what FENCE/BO_HANDLES already do.
--- a/sys/dev/drm/amd/amdgpu/amdgpu_cs.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_cs.c
@@ -179,6 +179,11 @@ static int amdgpu_cs_parser_init(struct amdgpu_cs_parser *p, union drm_amdgpu_cs
switch (p->chunks[i].chunk_id) {
case AMDGPU_CHUNK_ID_IB:
+ if (p->chunks[i].length_dw * sizeof(uint32_t)
+ < sizeof(struct drm_amdgpu_cs_chunk_ib)) {
+ ret = -EINVAL;
+ goto free_partial_kdata;
+ }
++num_ibs;
break;
Additionally, harden the byte-count computation at line 175 against the
size *= sizeof(uint32_t) 32-bit truncation: declare
size_t bytes = (size_t)p->chunks[i].length_dw * sizeof(uint32_t); and pass
bytes to both kvmalloc_array's effective byte count and copy_from_user.
(kvmalloc_array already takes count+elem separately and rejects
count*size_t overflow, so the alloc is fine; only the local unsigned size
truncation is buggy.)
Related findings
- DF-1483 (sibling): user-fence offset integer overflow in same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1484 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.9 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.9 KB | β raw |
| fix.diff | suggested-fix | git-apply-able minimal fix; compiles -Werror clean | 439 B | view raw |
| build.sh | build-script | echoes the module/kernel rebuild command | 384 B | view raw |
| run.sh | run-script | no live trigger on this guest | 300 B | view raw |
| env.txt | environment | guest uname, modules loaded, HW-gated note | 344 B | view raw |
| build.log | build-log | kernel build log excerpt proving -Werror clean compile of patched source | 399 B | view raw |
| fix_apply.log | apply-log | patch --dry-run output proving fix.diff applies cleanly on with-src | 407 B | view 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 |
PoC DF-1484: amdgpu_cs IB chunk missing size check (OOB read of kdata)
Class: heap OOB read
Cited site: sys/dev/drm/amd/amdgpu/amdgpu_cs.c:181-184, 999
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
No β amdgpu HW-gated as above. Trigger is a CS chunk with chunk_id=AMDGPU_CHUNK_ID_IB and length_dw too small for struct drm_amdgpu_cs_chunk_ib (32 bytes).
The bug is confirmed at the source level by tracing the cited path:line in
sys/dev/drm/amd/amdgpu/amdgpu_cs.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
amdgpu_cs_parser_bodies (181-184) AMDGPU_CHUNK_ID_IB case only does ++num_ibs with no minimum-size check, unlike FENCE (188) and BO_HANDLES (202). kdata is allocated as kvmalloc_array(length_dw, 4) at 169 β for length_dw < 8 (=32/4), the allocation is smaller than struct drm_amdgpu_cs_chunk_ib. amdgpu_cs_ib_fill:999 then casts kdata to struct drm_amdgpu_cs_chunk_ib * and reads its fields (ip_type, flags, etc.) β OOB read of the (small) kdata allocation.
Realistic impact ceiling
leak (info leak / DoS)
Fix
In the IB chunk case, check length_dw * sizeof(uint32_t) < sizeof(struct drm_amdgpu_cs_chunk_ib) and return -EINVAL like the FENCE/BO_HANDLES cases do.
See fix.diff for the git-apply-able patch.
How to validate the fix
# 1. Apply fix.diff against the in-guest source: scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1484.diff ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 < /root/DF-1484.diff' # 2. Rebuild the affected module (preferred) or a single-fix kernel: ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src/sys/sys/dev/drm/amd/amdgpu && make' # 3. The compile must succeed with -Werror (it does β see build.log).
VERDICT β DF-1484: amdgpu_cs IB chunk missing size check (OOB read of kdata)
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/amdgpu_cs.c:181-184, 999,
but the affected driver attaches only to hardware not present in the audit QEMU
guest, so it cannot be live-triggered here. The fix.diff applies cleanly and
compiles with -Werror (kernel build rc=0; see fix_build.log).
Mechanism (cited path β primitive β effect)
amdgpu_cs_parser_bodies (181-184) AMDGPU_CHUNK_ID_IB case only does ++num_ibs with no minimum-size check, unlike FENCE (188) and BO_HANDLES (202). kdata is allocated as kvmalloc_array(length_dw, 4) at 169 β for length_dw < 8 (=32/4), the allocation is smaller than struct drm_amdgpu_cs_chunk_ib. amdgpu_cs_ib_fill:999 then casts kdata to struct drm_amdgpu_cs_chunk_ib * and reads its fields (ip_type, flags, etc.) β OOB read of the (small) kdata allocation.
Reachability on this guest
No β amdgpu HW-gated as above. Trigger is a CS chunk with chunk_id=AMDGPU_CHUNK_ID_IB and length_dw too small for struct drm_amdgpu_cs_chunk_ib (32 bytes).
Phase 6 β escalation potential
This is a heap OOB read 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).
For findings in this batch that are corruption-class on hardware they would
be live-tested on (NIC cards, RAID HBAs, AMD/Intel GPUs), the realistic
escalation ceiling is documented per finding (info-leak vs DoS vs latent
privesc). No uid=0 claim is made β none is reachable on this guest.
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 ofmake -j6 nativekernel KERNCONF=X86_64_GENERIC(kernel build rc=0; affected module builds radeon.ko/amdgpu.ko/sound.ko/i915.ko/vga_switcheroo.ko all produced). - For musycc.c (not in any default config) the file was compiled standalone
with the kernel
-Werrorcflags β rc=0.
In the IB chunk case, check length_dw * sizeof(uint32_t) < sizeof(struct drm_amdgpu_cs_chunk_ib) and return -EINVAL like the FENCE/BO_HANDLES cases do.
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
- m
- d
- g
- p
- u
- _
- c
- s
- .
- c
- :
- 1
- 8
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- c
- s
- .
- c
- :
- 1
- 8
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- c
- s
- .
- c
- :
- 1
- 8
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- c
- s
- .
- c
- :
- 9
- 9
- 9
Detail
Exploit chain
none β amdgpu HW-gated (no AMD GPU in guest). Primitive is info-leak on real HW; no live escalation possible on this guest.
Evidence (decisive lines)
Source-level confirmation at sys/dev/drm/amd/amdgpu/amdgpu_cs.c:181, sys/dev/drm/amd/amdgpu/amdgpu_cs.c:184, sys/dev/drm/amd/amdgpu/amdgpu_cs.c:188. fix.diff applies cleanly (patch -p1 --forward: APPLIES_OK) and compiles -Werror clean as part of `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0; affected .o/.ko produced). No live trigger on this guest (HW/module gated).
PoC changes
Wrote VERDICT.md, fix.diff (one hunk: minimum-size check returning -EINVAL), build/run.sh, build.log excerpt, fix_apply.log, env.txt, manifest.json.
Verified recommended fix
In the AMDGPU_CHUNK_ID_IB case, check length_dw * sizeof(uint32_t) < sizeof(struct drm_amdgpu_cs_chunk_ib) and return -EINVAL (matching FENCE/BO_HANDLES). Supersedes any pre-verification proposal. The full git-apply-able diff lives in findings/poc/DF-1484/fix.diff.
Verdict
amdgpu_cs_parser_bodies AMDGPU_CHUNK_ID_IB case (181-184) only does ++num_ibs with no minimum-size check, unlike FENCE (188) and BO_HANDLES (202). kdata allocated as kvmalloc_array(length_dw, 4) at 169 β for length_dw < 8 the allocation is smaller than struct drm_amdgpu_cs_chunk_ib (32 bytes). amdgpu_cs_ib_fill at 999 casts kdata to that struct and reads fields β OOB read of the (small) kdata allocation. amdgpu HW-gated as DF-1467. Source-level confirmed.
No comments yet.