amdgpu_gem: GEM_VA ioctl passes unchecked offset_in_bo/map_size into amdgpu_vm_bo_map enabling offset+size wrap to corrupt GPU page tables
Summary
amdgpu_gem_va_ioctl at amdgpu_gem.c:644-646 (MAP) and 664-666 (REPLACE) passes args->offset_in_bo and args->map_size unmodified to amdgpu_vm_bo_map/replace_map. Sink at amdgpu_vm.c:2510-2512 and 2574-2577: if (saddr>=eaddr || (bo && offset+size>amdgpu_bo_size(bo))) - offset+size uint64_t NO wraparound guard. offset_in_bo=0xFFFFFFFFFFFFFFF0 + map_size=0x20 wraps to 0x10 < any BO>=16 bytes. mapping->offset=0xFFFFFFFFFFFFFFF0 at 2532. amdgpu_vm_bo_split_mapping pfn=mapping->offset>>PAGE_SHIFT=0x0FFFFFFFFFFFFFFF (amdgpu_vm.c:1988); single drm_mm_node loop while(pfn>=nodes->size){pfn-=nodes->size; ++nodes} at 1989-1993 walks past BO into adjacent kernel memory read as PTE source. Unpriv render-node. Linux upstream fixed by adding offset+size<offset guard.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1719 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness that reproduces the bug logic | 3.3 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 98 B | view raw |
| run.sh | run-script | ./harness | 61 B | view raw |
| build.log | build-log | full build output | 208 B | view raw |
| run.log | run-log | full decisive run output | 869 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, Phase 6, fix | 2.3 KB | β raw |
| fix.diff | suggested-fix | git-apply-able one-logical-change fix | 797 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 |
DF-1719 β amdgpu GEM_VA ioctl unchecked offset_in_bo/map_size β OOB GPU PT
Verdict
REPRODUCED (logic/harness) β bug confirmed by source trace and a
harness that demonstrates the missing (offset + size) < offset
wraparound guard at amdgpu_vm.c:2510-2511. Not live-triggerable on the
default QEMU guest: no AMD GPU present. The bug is a render-node
unprivileged reach on real AMD GPU hardware; the upstream Linux kernel
added the same wraparound guard this fix introduces.
Mechanism (path:line)
sys/dev/drm/amd/amdgpu/amdgpu_gem.c:644-646(MAP) and:664-666(REPLACE) passargs->offset_in_boandargs->map_sizestraight toamdgpu_vm_bo_map/amdgpu_vm_bo_replace_mapwith no wraparound guard.sys/dev/drm/amd/amdgpu/amdgpu_vm.c:2510-2512β sink bound check isif (saddr >= eaddr || (bo && offset + size > amdgpu_bo_size(bo))). Withoffset_in_bo = 0xFFFFFFFFFFFFFFF0andmap_size = 0x20,offset + sizewraps to0x10, less than any BO size β₯ 16 bytes.mapping->offset = 0xFFFFFFFFFFFFFFF0is then stored (amdgpu_vm.c:2532);amdgpu_vm_bo_split_mappingusespfn = mapping->offset >> PAGE_SHIFT = 0x0FFFFFFFFFFFFFFFand walks thedrm_mm_nodelist past the BO end, reading adjacent kernel memory as PTE source bytes.
Phase 6 escalation
Render-node reach on AMD GPU hardware. Requires a GPU; the default guest has none. The primitive is OOB kernel-memory read (used as PTE bytes) β primarily an info leak / KASLR-defeat class. Escalation to uid0 would require additional steps (e.g. shaping adjacent memory to control PTE contents β arbitrary physical page map β write primitive). Not pursued because the precondition (AMD GPU) is absent on the default guest.
PoC
harness.c reproduces the wraparound arithmetic with attacker inputs
offset_in_bo = 0xFFFFFFFFFFFFFFF0, map_size = 0x1000, bo_size = 4096.
The sink check accepts the input, the stored mapping->offset is OOB.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json,
fix.diff. Original folder was empty.
Fix
fix.diff adds the wraparound guard in amdgpu_gem_va_ioctl for MAP
and REPLACE: if (offset_in_bo >= offset_in_bo + map_size) return -EINVAL;.
Validated by a clean amdgpu.ko rebuild with the patch applied (the
existing module compiles + links, fix code present in amdgpu_gem.o).
Fix verification
fixedVALIDATED at module-build level: applied fix.diff to amdgpu source, 'make' rc=0, amdgpu.ko links cleanly with the new wraparound guard in amdgpu_gem.o. No live runtime test (no AMD GPU on default guest); validation is apply+compile+link. Module is loadable form; same path used by /boot/kernel/amdgpu.ko.
baseline: harness shows sink accepts attacker input (offset_in_bo=0xFFFFFFFFFFFFFFF0 + map_size=0x1000 wraps to 0xff0 < BO), stores mapping->offset OOB patched: amdgpu.ko builds clean with the (offset_in_bo >= offset_in_bo + map_size) guard added at amdgpu_gem.c:602 (just before INIT_LIST_HEAD); the guard rejects the wrap input at ioctl entry.
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
- _
- g
- e
- m
- .
- c
- :
- 6
- 4
- 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
- _
- g
- e
- m
- .
- c
- :
- 6
- 6
- 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
- _
- v
- m
- .
- c
- :
- 2
- 5
- 1
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- v
- m
- .
- c
- :
- 2
- 5
- 3
- 2
Detail
Exploit chain
Render-node reach on AMD GPU hardware. Primitive is OOB kernel-memory read used as GPU PTE bytes -> info leak / KASLR-defeat class. Escalation to uid0 would need additional steps (shape adjacent memory to control PTE contents -> arbitrary physical page map -> write primitive). Not pursued because the precondition (AMD GPU) is absent on the default guest. Harness in harness.c.
Evidence (decisive lines)
offset_in_bo + map_size = 0xff0 (wrapped) Stored: mapping->offset = 0xfffffffffffffff0 (OOB vs BO size 0x1000) VERDICT: BUG CONFIRMED. The missing (offset+size < offset) wraparound guard lets unprivileged render-node callers smuggle in a mapping whose offset is far past the BO, yielding OOB reads of adjacent kernel memory as PTE source bytes in amdgpu_vm_bo_split_mapping.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.
Verified recommended fix
fix.diff adds the wraparound guard in amdgpu_gem_va_ioctl for MAP/REPLACE: if (args->offset_in_bo >= args->offset_in_bo + args->map_size) return -EINVAL. Mirrors the upstream Linux fix. Supersedes finding proposal (finding cited the upstream fix but gave no concrete patch).
Verdict
REPRODUCED (logic/harness). amdgpu_gem.c:644 (MAP) and :664 (REPLACE) pass args->offset_in_bo/map_size to amdgpu_vm_bo_map unchecked. Sink at amdgpu_vm.c:2510-2511 checks only (offset+size > bo_size) with no (offset+size < offset) wraparound guard. With offset_in_bo=0xFFFFFFFFFFFFFFF0 + map_size=0x20, offset+size wraps to 0x10 < any BO size >=16; mapping->offset stored as 0xFFFFFFFFFFFFFFF0 (line 2532), subsequent split_mapping walks past BO into adjacent kernel memory read as PTE source. Harness reproduces the wraparound arithmetic exactly. Render-node reach on real AMD GPU; the default QEMU guest has no AMD GPU so no live trigger.
No comments yet.