DF-4405 / fw_hdr_overflow.c
/* * DF-4405 — amdgpu_uvd_sw_init/amdgpu_uvd_resume trust unvalidated * ucode_size_bytes / ucode_array_offset_bytes (DF-1946 family) * * Target: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:195-202 (validate call), * sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:250-253 (bo_size PAGE_ALIGN wrap), * sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:403-411 (resume memcpy_toio / * memset_io with underflowed length). * Root of family: amdgpu_ucode_validate() only checks fw->datasize == * hdr->size_bytes (sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260 = DF-1946); * the per-field consumers were never bounds-checked in this file. * * This program replicates the exact u32 arithmetic of both kernel sites for a * synthetic firmware header that PASSES amdgpu_ucode_validate * (size_bytes == datasize) while carrying ucode_array_offset_bytes = 0x1000 * and ucode_size_bytes = 0xFFFFFFF8, and prints the lengths the kernel would * use for the VRAM copy / memset. Deterministic; runs anywhere. * * Build: cc -O2 -o fw_hdr_overflow fw_hdr_overflow.c * Run: ./fw_hdr_overflow */ #include <stdio.h> #include <stdint.h> /* replicate DFly amdgpu GPU page align on 4K pages (64-bit unsigned long) */ #define AMDGPU_GPU_PAGE_ALIGN(x) (((x) + 4095UL) & ~4095UL) struct cfh { uint32_t ucode_array_offset_bytes; uint32_t ucode_size_bytes; uint32_t size_bytes; }; /* the ONLY validation the kernel performs (amdgpu_ucode.c:251-260) */ static int amdgpu_ucode_validate_replica(size_t fw_datasize, uint32_t hdr_size_bytes) { return fw_datasize == hdr_size_bytes ? 0 : -1; } int main(void) { const size_t fw_datasize = 0x8000; /* a 32 KiB firmware blob */ struct cfh hdr = { .ucode_array_offset_bytes = 0x1000, .ucode_size_bytes = 0xFFFFFFF8u, .size_bytes = 0x8000, /* == datasize: PASSES */ }; uint32_t ucode_size = hdr.ucode_size_bytes; uint32_t offset = hdr.ucode_array_offset_bytes; unsigned long bo_size, size; printf("== DF-4405: amdgpu_uvd fw-header arithmetic replica ==\n\n"); printf("firmware blob datasize = 0x%zx\n", fw_datasize); printf("hdr->size_bytes = 0x%x\n", hdr.size_bytes); printf("amdgpu_ucode_validate() = %d (0 == ACCEPTED)\n\n", amdgpu_ucode_validate_replica(fw_datasize, hdr.size_bytes)); printf("hdr->ucode_array_offset_bytes = 0x%x\n", offset); printf("hdr->ucode_size_bytes = 0x%x\n\n", ucode_size); /* --- amdgpu_uvd.c:250-253 (sw_init, non-PSP load) --- */ bo_size = (200 * 1024) + (256 * 1024); /* STACK + HEAP, amdgpu_uvd.h:29-30 */ printf("[sw_init ] bo_size += AMDGPU_GPU_PAGE_ALIGN(" "ucode_size_bytes + 8)\n"); printf(" u32 add first: 0xFFFFFFF8 + 8 = 0x%x (wraps to 0)\n", (uint32_t)(ucode_size + 8u)); printf(" PAGE_ALIGN(0) = 0x%lx\n", AMDGPU_GPU_PAGE_ALIGN((unsigned long)(uint32_t)(ucode_size + 8u))); printf(" => bo_size = 0x%lx (200K+256K): allocated WITHOUT any" " firmware space\n\n", bo_size); /* --- amdgpu_uvd.c:403-411 (resume, saved_bo == NULL) --- */ size = bo_size; printf("[resume ] memcpy_toio(cpu_addr, fw->data + 0x%x, 0x%x)\n", offset, ucode_size); printf(" kernel reads 0x%x (%.2f GiB) bytes starting" " 0x%x into a 0x%zx-byte blob\n", ucode_size, ucode_size / 1073741824.0, offset, fw_datasize); printf(" OOB READ = 0x%x - (0x%zx - 0x%x) = %u bytes" " past the firmware buffer\n\n", ucode_size, fw_datasize, offset, (unsigned)(ucode_size - (uint32_t)(fw_datasize - offset))); printf(" then 'size -= ucode_size_bytes':" " 0x%lx - 0x%x -> 0x%x (u32 underflow)\n", size, ucode_size, (uint32_t)(size - ucode_size)); printf(" memset_io(ptr, 0, 0x%x): %lu bytes past the" " 0x%lx-byte bo end\n\n", (uint32_t)(size - ucode_size), (unsigned long)((uint32_t)(size - ucode_size)) - size, size); printf("Both lengths derive solely from unvalidated header fields; the" " only gate, amdgpu_ucode_validate(), checked a different field" " (size_bytes). Preconditions: attacker-controlled firmware file" " (root-installed, /boot/modules/amdgpufw_*_uvd) on AMD hardware" " -> Low severity, DF-1946 family instance in amdgpu_uvd.c.\n"); return 0; } |