DragonFlyBSD Kernel Audit
DF-1719 / harness.c
← back to finding ↓ download raw
/*
 * 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;
}