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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1719 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download 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) pass args->offset_in_bo and args->map_size straight to amdgpu_vm_bo_map / amdgpu_vm_bo_replace_map with no wraparound guard.
  • sys/dev/drm/amd/amdgpu/amdgpu_vm.c:2510-2512 β€” sink bound check is if (saddr >= eaddr || (bo && offset + size > amdgpu_bo_size(bo))). With offset_in_bo = 0xFFFFFFFFFFFFFFF0 and map_size = 0x20, offset + size wraps to 0x10, less than any BO size β‰₯ 16 bytes.
  • mapping->offset = 0xFFFFFFFFFFFFFFF0 is then stored (amdgpu_vm.c:2532); amdgpu_vm_bo_split_mapping uses pfn = mapping->offset >> PAGE_SHIFT = 0x0FFFFFFFFFFFFFFF and walks the drm_mm_node list 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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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.
↓ fix.diffamdgpu.ko module rebuild (loadable .ko, not in GENERIC) - apply fix.diff to /usr/src/sys/dev/drm/amd/amdgpu, 'make -j1' rc=0, amdgpu.ko 101576880 bytes built clean

Confirmed kernel references

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.