⬢ DragonFlyBSD Kernel Audit
DF-3666 / df3666.c
← back to finding ↓ download raw
/*
 * DF-3666 PoC: faithful index-math reproduction of the undersized RLC
 * save/restore buffer object in sumo_rlc_init()
 * (sys/dev/drm/radeon/evergreen.c:4155 allocation vs 4197-4206 writes).
 *
 * For the ON/LN/TN (pre-TAHITI IGP: PALM/SUMO/SUMO2) path the function
 * allocates:
 *
 *   evergreen.c:4155  radeon_bo_create(rdev, dws * 4, PAGE_SIZE, ...)
 *                     with dws = rdev->rlc.reg_list_size
 *                     = ARRAY_SIZE(sumo_rlc_save_restore_register_list) = 81
 *                     -> a 324-byte BO
 *
 * but the packing loop writes the 2-regs-into-3-dwords format:
 *
 *   evergreen.c:4197  for (i = 0; i < dws; i++) {
 *                         data = src_ptr[i] >> 2;
 *                         i++;
 *                         if (i < dws) data |= (src_ptr[i] >> 2) << 16;
 *                         j = (((i - 1) * 3) / 2);
 *                         dst_ptr[j] = cpu_to_le32(data);   <-- dword 3k
 *                     }
 *   evergreen.c:4205  j = ((i * 3) / 2);
 *                     dst_ptr[j] = END_MARKER;              <-- highest idx
 *
 * which requires ceil(dws/2)*3 + 1 dwords = 124 dwords = 496 bytes.
 *
 * The overrun is 43 dwords / 172 bytes past the BO's declared size.  In
 * practice it is contained by TTM page-granularity (bo->num_pages rounds
 * 324 bytes up to 1 page, and 496 < 4096), so it corrupts only slack
 * inside the object's own backing page -- which is why this is filed as
 * Low / hardening rather than an exploitable heap overflow.
 *
 * The RLC GPU microcode subsequently uses these save areas at runtime
 * (evergreen.c:4394 TN_RLC_SAVE_AND_RESTORE_BASE), so the GPU-side saves
 * also land in the undersized region.
 *
 * Build: cc -O2 -o df3666 df3666.c
 * Run:   ./df3666
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define RLC_SAVE_RESTORE_LIST_END_MARKER    0x00000000

int main(void)
{
	const int dws = 81;		/* ARRAY_SIZE(sumo_rlc_save_restore_register_list),
					   evergreen.c:123-206 */
	const uint32_t alloc_bytes = (uint32_t)dws * 4;	/* evergreen.c:4155 */
	/* TTM: bo->num_pages = (size + PAGE_SIZE-1) >> PAGE_SHIFT */
	const uint32_t backing_bytes = (alloc_bytes + 4095) & ~4095u;

	/* dst model of the mapped BO (page-sized backing, as TTM provides) */
	uint32_t *dst = calloc(backing_bytes / 4, 4);
	uint32_t *src = malloc(alloc_bytes);
	uint32_t data, i, j;
	int max_idx = -1, oob_dwords = 0;

	memset(src, 0xAB, alloc_bytes);

	/* ---- verbatim loop of evergreen.c:4197-4204 ---- */
	for (i = 0; i < (uint32_t)dws; i++) {
		data = src[i] >> 2;
		i++;
		if (i < (uint32_t)dws)
			data |= (src[i] >> 2) << 16;
		j = (((i - 1) * 3) / 2);
		dst[j] = data;
		if ((int)j > max_idx) max_idx = (int)j;
	}
	/* ---- verbatim epilogue of evergreen.c:4205-4206 ---- */
	j = ((i * 3) / 2);
	dst[j] = RLC_SAVE_RESTORE_LIST_END_MARKER;
	if ((int)j > max_idx) max_idx = (int)j;

	/* every dword in [dws, max_idx] is past the 324-byte BO */
	oob_dwords = max_idx - dws + 1;

	printf("== DF-3666: sumo_rlc_init save/restore BO sizing ==\n\n");
	printf("reg_list entries (dws)          : %d\n", dws);
	printf("BO allocated (evergreen.c:4155) : %u bytes (%u dwords)\n",
	       alloc_bytes, alloc_bytes / 4);
	printf("loop exit i                     : %u\n", i);
	printf("end-marker index (evergreen.c:4205): %u\n", j);
	printf("highest dword written           : %d (byte offset %d)\n",
	       max_idx, max_idx * 4);
	printf("bytes actually required         : %d\n", (max_idx + 1) * 4);
	printf("OOB write past BO size          : %d dwords = %d bytes\n",
	       oob_dwords, oob_dwords * 4);
	printf("TTM backing (page-rounded)      : %u bytes -> overrun %s "
	       "the object's own backing page\n", backing_bytes,
	       ((max_idx + 1) * 4 <= (int)backing_bytes) ?
	       "stays inside" : "ESCAPES");

	if ((max_idx + 1) * 4 > (int)alloc_bytes) {
		printf("\nRESULT: BUG REPRODUCED - writes need %d bytes but the "
		       "BO is %u bytes.\n", (max_idx + 1) * 4, alloc_bytes);
		printf("Contained only by TTM page rounding; a latent "
		       "CWE-787 that must be fixed at the allocation site.\n");
		return 0;
	}
	printf("\nRESULT: no overrun?!\n");
	return 1;
}