โฌข DragonFlyBSD Kernel Audit
DF-4924 / df4924_trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-4924 harness โ€” gfx_v9_0 gfx-ring frame-reservation undercount for the
 * TMZ FRAME_CONTROL emission consumed by amdgpu_ib_schedule().
 *
 * Arithmetic/write-pattern replica (same method as the DF-4864 evidence
 * pack): it reproduces, with the EXACT constants from the guest's kernel
 * source, the reservation math of amdgpu_ib_schedule()
 * (sys/dev/drm/amd/amdgpu/amdgpu_ib.c:159-166) and the ring-write
 * discipline of amdgpu_ring_write() (amdgpu_ring.h:276-283), and shows that
 * for gfx-ring submissions with num_ibs == 24 (mod 64) the emission stream
 * exceeds the ring allocation by exactly 2 dwords (the unaccounted
 * gfx_v9_0_ring_emit_tmz FRAME_CONTROL packet: gfx_v9_0.c:4780 +
 * amdgpu_ib.c:231-232).
 *
 * Constants transcribed from the tree (path:line in comments):
 *   emit_frame_size (gfx_v9_0.c:4745-4763) = 160 dw  (worst case, all terms)
 *   emit_ib_size    (gfx_v9_0.c:4765)      = 4 dw
 *   align_mask      (gfx_v9_0.c:4739)      = 0xff -> ring_alloc aligns to 256 dw
 *   emit_tmz writes (gfx_v9_0.c gfx_v9_0_ring_emit_tmz) = 2 dw
 *   SOC15_FLUSH_GPU_TLB_NUM_WREG     = 4   (soc15.h:31)
 *   SOC15_FLUSH_GPU_TLB_NUM_REG_WAIT = 1   (soc15.h:32)
 *
 * Build:  cc -O2 -o df4924_trigger df4924_trigger.c
 * Run:    ./df4924_trigger
 * Success criterion: baseline destroys exactly 2 canary dwords iff
 * num_ibs % 64 == 24; fixed accounting destroys none for any num_ibs.
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

/* --- transcribed kernel constants ------------------------------------ */
/* gfx_v9_0.c:4745-4763 emit_frame_size, term by term */
#define T_COND_EXEC_1        5   /* COND_EXEC                          */
#define T_PIPELINE_SYNC      7   /* PIPELINE_SYNC                      */
#define SOC15_NUM_WREG       4   /* soc15.h:31                          */
#define SOC15_NUM_REG_WAIT   1   /* soc15.h:32                          */
#define T_WREG_PER           5
#define T_REGWAIT_PER        7
#define T_VM_FLUSH           2
#define T_FENCE_VM_FLUSH     8
#define T_GDS_SWITCH        20
#define T_DOUBLE_SB          4
#define T_COND_EXEC_2        5
#define T_HDP_FLUSH          7
#define T_VGT_FLUSH          4
#define T_CE_META           14
#define T_DE_META           31
#define T_CNTX_CTRL          3
#define T_HDP_INVL           5
#define T_FENCE_X2          (8 + 8) /* uf fence + real fence            */
#define T_SWITCH_BUFFER      2
#define T_TMZ_UNACCOUNTED    2   /* gfx_v9_0_ring_emit_tmz: NOT in sum */

#define EMIT_IB_SIZE         4   /* gfx_v9_0.c:4765 */
#define ALIGN_MASK         0xff /* gfx_v9_0.c:4739 */
#define TMZ_ENABLED          1  /* gfx_v9_0.c:4780 .emit_tmz is set   */

static unsigned frame_size(int with_tmz_fix)
{
	unsigned f = T_COND_EXEC_1 + T_PIPELINE_SYNC +
		SOC15_NUM_WREG * T_WREG_PER +
		SOC15_NUM_REG_WAIT * T_REGWAIT_PER +
		T_VM_FLUSH + T_FENCE_VM_FLUSH + T_GDS_SWITCH + T_DOUBLE_SB +
		T_COND_EXEC_2 + T_HDP_FLUSH + T_VGT_FLUSH + T_CE_META +
		T_DE_META + T_CNTX_CTRL + T_HDP_INVL + T_FENCE_X2 +
		T_SWITCH_BUFFER;
	if (with_tmz_fix)
		f += T_TMZ_UNACCOUNTED;
	return f;
}

/* amdgpu_ring_alloc (amdgpu_ring.c:64-76): align up; result is count_dw */
static unsigned ring_alloc(unsigned frame, unsigned num_ibs)
{
	unsigned ndw = frame + num_ibs * EMIT_IB_SIZE;
	return (ndw + ALIGN_MASK) & ~ALIGN_MASK;
}

/*
 * amdgpu_ring_write (amdgpu_ring.h:276-283): logs an error when count_dw
 * <= 0 but writes the dword anyway. Emulated against a canaried buffer
 * exactly alloc_dw dwords long, canary zone at/after alloc_dw.
 */
#define MAXBUF 4096
static uint32_t ringbuf[MAXBUF];
static int count_dw, wptr, writes_past_end;

static void ring_write(unsigned v, unsigned alloc_dw)
{
	if (count_dw <= 0)
		writes_past_end++;	/* the DRM_ERROR path โ€” write happens */
	ringbuf[wptr++] = v;
	count_dw--;
}

/* one worst-case gfx CS submission; emission totals per amdgpu_ib_schedule */
static int submit(unsigned num_ibs, int with_tmz_fix)
{
	unsigned frame = frame_size(with_tmz_fix);
	unsigned alloc_dw = ring_alloc(frame, num_ibs);
	unsigned emitted = 0, i, destroyed = 0;

	if (alloc_dw + 8 > MAXBUF)
		return -1;
	memset(ringbuf, 0xCA, sizeof(ringbuf));	/* 0xCACACACA canaries */
	count_dw = (int)alloc_dw;
	wptr = 0;
	writes_past_end = 0;

	emitted += T_COND_EXEC_1;		/* ib.c:194-195 init_cond_exec */
	emitted += T_PIPELINE_SYNC;		/* ib.c:169-181 */
	emitted += SOC15_NUM_WREG * T_WREG_PER +
		SOC15_NUM_REG_WAIT * T_REGWAIT_PER +
		T_VM_FLUSH + T_FENCE_VM_FLUSH + T_VGT_FLUSH; /* vm_flush */
	emitted += T_DOUBLE_SB;			/* vm_flush double SB */
	emitted += T_COND_EXEC_2;		/* vm.c cond_exec */
	emitted += T_HDP_FLUSH;			/* ib.c:201-204 */
	emitted += T_CNTX_CTRL + T_CE_META + T_DE_META; /* ib.c:208-214 */
	for (i = 0; i < num_ibs; i++)		/* ib.c:216-229 */
		emitted += EMIT_IB_SIZE;
	if (TMZ_ENABLED)
		emitted += T_TMZ_UNACCOUNTED;	/* ib.c:231-232 โ€” the bug */
	emitted += T_HDP_INVL;			/* ib.c:234-237 */
	emitted += 8;				/* uf fence  ib.c:243-246 */
	emitted += 8;				/* real fence ib.c:248 */
	emitted += T_GDS_SWITCH + T_SWITCH_BUFFER; /* gds + SB tail */

	for (i = 0; i < emitted; i++)
		ring_write(0xDEADBEEF, alloc_dw);

	for (i = alloc_dw; i < alloc_dw + 8; i++)	/* canary check */
		if (ringbuf[i] != 0xCACACACA)
			destroyed++;

	if (destroyed || writes_past_end)
		printf("num_ibs=%3u reserved=%4u emitted=%4u writes_past_count=%d canary_dwords_destroyed=%d\n",
		       num_ibs, alloc_dw, emitted, writes_past_end, destroyed);
	return destroyed ? 1 : 0;
}

int main(void)
{
	int n, hits = 0, with_fix = 0;
	int hit_n[64]; int nhit = 0;

	printf("== baseline accounting (tree as-is: TMZ NOT in emit_frame_size) ==\n");
	printf("frame_size=%u emit_ib_size=%u align_mask=0x%x tmz=+%u dw\n",
	       frame_size(0), EMIT_IB_SIZE, ALIGN_MASK, T_TMZ_UNACCOUNTED);
	for (n = 1; n <= 200; n++)
		if (submit(n, 0) == 1) {
			hits++;
			if (nhit < 64) hit_n[nhit++] = n;
		}
	printf("baseline: %d overrun submission(s) in num_ibs 1..200: ", hits);
	for (n = 0; n < nhit; n++) printf("%d ", hit_n[n]);
	printf("\nrule: reserved = ALIGN(160 + 4*n, 256); emitted = 160 + 4*n + 2\n");
	printf("      overrun iff (160 + 4*n) %% 256 == 0 iff n %% 64 == 24\n");

	for (n = 1; n <= 200; n++)
		if (submit(n, 1) == 1)
			with_fix++;
	printf("== fixed accounting (emit_frame_size += 2 for TMZ) ==\n");
	printf("fixed: %d overrun submission(s) in num_ibs 1..200 (expect 0)\n",
	       with_fix);

	if (hits > 0 && with_fix == 0 && nhit >= 3 &&
	    hit_n[0] == 24 && hit_n[1] == 88 && hit_n[2] == 152) {
		printf("VERDICT: baseline overruns by exactly 2 dwords iff "
		       "num_ibs %% 64 == 24; fix eliminates all. REPLICATED.\n");
		return 0;
	}
	printf("VERDICT: unexpected result.\n");
	return 1;
}