⬢ DragonFlyBSD Kernel Audit
DF-4967 / gart_math.c
← back to finding ↓ download raw
/*
 * gart_math.c - arithmetic validation for the two radeon_gart.c findings.
 *
 * Not a kernel PoC: this reproduces, in userland with the exact constant
 * types used by the kernel code, the arithmetic of:
 *
 *   F1 (offset truncation):
 *     sys/dev/drm/radeon/radeon_device.c:1124-1137  radeon_check_arguments()
 *       - accepts radeon_gart_size (MB) that is a power of two and >= 32,
 *         with NO upper bound; gtt_size = (u64)radeon_gart_size << 20
 *     sys/dev/drm/radeon/radeon_ttm.c:940-943
 *       - GTT heap = ttm_bo_init_mm(TTM_PL_TT, gtt_size >> PAGE_SHIFT)
 *     sys/dev/drm/radeon/radeon_ttm.c:656,659
 *       - offset = bo_mem->start << PAGE_SHIFT  (unsigned long, 64-bit)
 *         passed to radeon_gart_bind(rdev, unsigned offset, ...)  (32-bit!)
 *     sys/dev/drm/radeon/radeon_gart.c:299
 *       - t = offset / RADEON_GPU_PAGE_SIZE   (truncated index)
 *
 *   F2 (table_size wrap - DF-2012 radeon twin):
 *     sys/dev/drm/radeon/r600.c:1115, rs600.c:546, r100.c:647, r300.c:136
 *       - table_size = num_gpu_pages * 8 (or *4) computed in 32-bit unsigned
 *     sys/dev/drm/radeon/radeon_gart.c:345-350
 *       - num_cpu_pages/num_gpu_pages are 'unsigned' (u32) fields assigned
 *         from u64 gtt_size with no overflow check
 *
 * Build:  cc -O2 -o gart_math gart_math.c
 * Run:    ./gart_math
 * Success criterion: every CHECK(...) line prints PASS.
 */
#include <stdio.h>
#include <stdint.h>

#define PAGE_SHIFT 12
#define RADEON_GPU_PAGE_SIZE 4096u

static int failures;

static int is_pot(int v) { return v > 0 && (v & (v - 1)) == 0; }

/* radeon_check_arguments (radeon_device.c:1117-1134): power of two, >= 32, NO
 * upper bound. Returns accepted/rejected exactly like the kernel. */
static int check_gart_size_accepted(int radeon_gart_size /* MB */)
{
	if (radeon_gart_size < 32)
		return 0;
	if (!is_pot(radeon_gart_size))
		return 0;
	return 1;
}

static void check(const char *what, int cond)
{
	printf("%-78s %s\n", what, cond ? "PASS" : "FAIL");
	if (!cond)
		failures++;
}

int main(void)
{
	printf("== F1: gart_size=8192MB (8 GiB GTT) -> 32-bit offset truncation ==\n");

	/* Tunable accepted by radeon_check_arguments? */
	check("radeon_gart_size=8192 passes kernel validation (pot, >=32, no upper bound)",
	      check_gart_size_accepted(8192));

	{
		/* radeon_gart_init (radeon_gart.c:345-346): u64 -> unsigned */
		uint64_t gtt_size = (uint64_t)8192 << 20;      /* 8 GiB */
		unsigned num_cpu_pages = gtt_size / 4096;      /* 2^21  */
		unsigned num_gpu_pages = gtt_size / RADEON_GPU_PAGE_SIZE;
		unsigned table_size = num_gpu_pages * 8;       /* 16 MiB, no wrap */

		check("num_cpu_pages == 2^21 (heap spans > 4GiB of aperture offsets)",
		      num_cpu_pages == 1u << 21);
		check("table_size == 16 MiB (table itself fine - the bug is elsewhere)",
		      table_size == 16u * 1024u * 1024u);

		/* radeon_ttm_backend_bind: offset = (unsigned long)(start << PAGE_SHIFT) */
		unsigned long start_low  = 0;                  /* first page  */
		unsigned long start_high = 1ul << 20;          /* page 2^20 = byte offset 4 GiB */

		/* ... then radeon_gart_bind(struct radeon_device *, unsigned offset, ...)
		 * truncates to 32 bit, and t = offset / RADEON_GPU_PAGE_SIZE:
		 */
		unsigned off_low  = (unsigned)(start_low  << PAGE_SHIFT);
		unsigned off_high = (unsigned)(start_high << PAGE_SHIFT);
		unsigned t_low  = off_low  / RADEON_GPU_PAGE_SIZE;
		unsigned t_high = off_high / RADEON_GPU_PAGE_SIZE;

		check("byte offset of start_high is 4 GiB (fits only u64)",
		      (start_high << PAGE_SHIFT) == 1ull << 32);
		check("truncated 32-bit offset of start_high wraps to 0",
		      off_high == 0);
		check("GART entry index computed for both BOs is IDENTICAL (aliasing)",
		      t_low == t_high && t_low == 0);
		/* both BOs' bind() write pages[p]/pages_entry[t]/set_page(t) at p==t==0:
		 * the high BO's PTE overwrites the low BO's PTE -> the GPU translates
		 * the LOW BO's aperture addresses to the HIGH BO's physical page.
		 */
		(void)num_cpu_pages;
	}

	printf("\n== F2: gart_size=2097152MB (2 TiB) -> table_size wrap (DF-2012 twin) ==\n");

	check("radeon_gart_size=2097152 passes kernel validation (pot, >=32, no upper bound)",
	      check_gart_size_accepted(2097152));

	{
		uint64_t gtt_size = (uint64_t)2097152 << 20;   /* 2 TiB */
		unsigned num_gpu_pages = gtt_size / RADEON_GPU_PAGE_SIZE;
		unsigned table_size = num_gpu_pages * 8;       /* 32-bit mult */

		check("num_gpu_pages == 536870912 (0x20000100-would-be; here 2^29)",
		      num_gpu_pages == 536870912u);
		check("table_size wraps to 0 (2^29 * 8 == 2^32 mod 2^32)",
		      table_size == 0);
	}

	{
		/* r100/r300/rs400/rv370 (x4 tables): wrap at gtt >= 4 TiB */
		uint64_t gtt_size = (uint64_t)4194304 << 20;   /* 4 TiB */
		unsigned num_gpu_pages = gtt_size / RADEON_GPU_PAGE_SIZE;
		unsigned table_size = num_gpu_pages * 4;
		check("x4-asic: num_gpu_pages == 2^30, table_size wraps to 0",
		      num_gpu_pages == 1u << 30 && table_size == 0);
	}

	{
		/* gtt >= 16 TiB: num_gpu_pages itself truncates to 0 in the u32 field */
		uint64_t gtt_size = (uint64_t)(1 << 24) << 20; /* 16 TiB */
		unsigned num_gpu_pages = gtt_size / RADEON_GPU_PAGE_SIZE;
		check("num_gpu_pages u32 field truncates to 0 at gtt >= 16 TiB",
		      num_gpu_pages == 0);
	}

	printf("\n%s (%d failure%s)\n", failures ? "SOME CHECKS FAILED" : "ALL CHECKS PASSED",
	       failures, failures == 1 ? "" : "s");
	return failures ? 1 : 0;
}