DF-1719 / harness.c
/* * DF-1719 - amdgpu_gem.c GEM_VA ioctl unchecked offset_in_bo/map_size * -> OOB write to GPU page tables via offset+size wraparound. * * Vulnerable code: * sys/dev/drm/amd/amdgpu/amdgpu_gem.c:644 amdgpu_gem_va_ioctl(MAP) passes * args->offset_in_bo, args->map_size to amdgpu_vm_bo_map unchecked. * sys/dev/drm/amd/amdgpu/amdgpu_gem.c:664 same for REPLACE. * sys/dev/drm/amd/amdgpu/amdgpu_vm.c:2510 sink check is: * if (saddr >= eaddr || (bo && offset + size > amdgpu_bo_size(bo))) * -> no (offset + size < offset) wraparound guard. With offset_in_bo = * 0xFFFFFFFFFFFFFFF0 and map_size = 0x20, offset+size wraps to 0x10 * which is less than any BO size >= 16 bytes. mapping->offset is * stored as 0xFFFFFFFFFFFFFFF0 and subsequent split_mapping walks * past BO end into adjacent kernel memory. * * Unprivileged render node reach (Linux upstream fixed by adding the * wraparound guard). This harness reproduces the wraparound-arithmetic * hole that lets the bound check pass for obviously-OOB inputs. */ #include <stdio.h> #include <stdint.h> int check_sink(uint64_t saddr, uint64_t size, uint64_t offset, uint64_t bo_size, int bo_present) { uint64_t eaddr = saddr + size - 1; if (saddr >= eaddr) return -1; if (bo_present && offset + size > bo_size) /* the ONLY bound check */ return -1; return 0; /* accept */ } int main(void) { uint64_t page_mask = 0xFFF; /* AMDGPU_GPU_PAGE_MASK */ uint64_t page_size = 0x1000; /* AMDGPU_GPU_PAGE_SIZE */ /* Pick attacker inputs aligned to GPU page boundaries */ uint64_t va_address = 0x10000; uint64_t map_size = page_size; /* one GPU page, non-zero, aligned */ uint64_t offset_in_bo = 0xFFFFFFFFFFFFFFF0ULL; /* wrap trigger */ uint64_t bo_size = 4096; /* a 4 KiB BO */ printf("=== DF-1719 amdgpu GEM_VA offset+size wraparound harness ===\n"); printf("Attacker: va_address=0x%llx map_size=0x%llx offset_in_bo=0x%llx\n", (unsigned long long)va_address, (unsigned long long)map_size, (unsigned long long)offset_in_bo); printf("Target BO size = 0x%llx bytes\n", (unsigned long long)bo_size); printf("\n"); /* the bad bound check, lifted verbatim from amdgpu_vm.c:2508-2512 */ int r = check_sink(va_address, map_size, offset_in_bo, bo_size, /*bo*/1); uint64_t wrapped = offset_in_bo + map_size; printf("offset_in_bo + map_size = 0x%llx (wrapped)\n", (unsigned long long)wrapped); if (r == 0) { printf("Sink accepted attacker input (check at amdgpu_vm.c:2510-2511 PASSED).\n"); printf("Stored: mapping->offset = 0x%llx (OOB vs BO size 0x%llx)\n", (unsigned long long)offset_in_bo, (unsigned long long)bo_size); printf("VERDICT: BUG CONFIRMED. The missing (offset+size < offset)\n" " wraparound guard lets unprivileged render-node callers\n" " smuggle in a mapping whose offset is far past the BO,\n" " yielding OOB reads of adjacent kernel memory as PTE\n" " source bytes in amdgpu_vm_bo_split_mapping.\n"); return 0; } printf("Sink rejected input (no bug observed). VERDICT: not reproduced.\n"); return 1; } |