/*
 * acsim.c -- core kernel-compiled harness for the DF-2372..DF-2399
 * acpica/agp verification batch ("ACHARNESS" kernel).
 *
 * Provides a sysctl command channel (debug.acsim.op, string) plus a
 * readable log (debug.acsim.log).  Everything runs REAL in-kernel
 * production code; this file only drives it:
 *
 *   op=pciub32                 DF-2372: AcpiOsReadPciConfiguration
 *                              Width=32 vs 16 vs raw pci_cfgregread on
 *                              0:0.0 (the (1 << 32) UB mask).
 *   op=pkg_oob_canary          DF-2385: acpi_PkgInt idx==Count reads the
 *                              planted canary element (no bounds check).
 *   op=pkg_oob_page            DF-2385: acpi_PkgInt idx==Count read lands
 *                              on the first byte of an UNMAPPED page
 *                              (verifiable heap OOB, trap 12).
 *   op=hookreg / op=hookchurn_start / op=hookchurn_stop / op=hookstat
 *                              DF-2374: registered /dev/acpi ioctl hook
 *                              churned against acpi_deregister_ioctl
 *                              (dereg + canary-refill + re-reg loop in a
 *                              rig kthread; userland spams the ioctl).
 *   op=poison:SIZE:N           spray N kmalloc(SIZE) chunks filled with
 *                              0x41 then free them (zone stays canaried).
 *   op=check:SIZE:OFF          kmalloc(SIZE), report 8 bytes at OFF,free.
 *   op=deldev:DEVCLASS:UNIT    device_delete_child(parent, dev) -- runs
 *                              the REAL detach and frees dev + softc.
 *   op=eval:PATH               AcpiEvaluateObject(NULL, PATH, NULL, NULL)
 *                              (fires AML Notify / flips AML globals).
 *   op=blockq / op=blockw      queue a blocker onto the real acpi_task
 *                              thread / wake it (FIFO choreography).
 *   op=devstat:DEVCLASS:UNIT   kprintf device state + busy count.
 */

#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/bus_private.h>
MALLOC_DECLARE(M_BUS);
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/lock.h>
#include <sys/kthread.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <vm/vm_map.h>

#include "acpi.h"
#include "accommon.h"
#include <dev/acpica/acpivar.h>
#include <dev/acpica/acpiio.h>
#include <sys/bus_private.h>
#include <bus/pci/pci_cfgreg.h>
#include <bus/pci/pcireg.h>

MALLOC_DEFINE(M_ACSIM, "acsim", "ACSIM verification rig");

#define ACSIM "ACSIM: "

static int	acsim_op_sysctl(SYSCTL_HANDLER_ARGS);
static int	acsim_log_sysctl(SYSCTL_HANDLER_ARGS);

static char		acsim_log[16384];
static size_t		acsim_log_len;
static struct lock	acsim_log_lock;

static struct thread	*acsim_churn_td;
static volatile int	acsim_churn_run;
static volatile int	acsim_hook_alive;
static u_long		acsim_hook_cmd = 0xDF740001UL;
static long		acsim_hook_calls;
static long		acsim_hook_regs;
static long		acsim_hook_deregs;

SYSCTL_PROC(_debug, OID_AUTO, acsim_op, CTLTYPE_STRING | CTLFLAG_RW,
    0, 0, acsim_op_sysctl, "A", "ACSIM command channel");
SYSCTL_PROC(_debug, OID_AUTO, acsim_log, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, acsim_log_sysctl, "A", "ACSIM log");

static void	acsim_note(const char *fmt, ...) __printflike(1, 2);
static void	acsim_blocker_fn(void *arg);
static void	acsim_run_ufw(void);
static void	acsim_run_fujitsu(void);
static void	acsim_run_batblock(void);
static void	acsim_free_softc(const char *dcname, int unit);
static void	acsim_run_fufreed(void);
static void	acsim_run_tosfreed(void);
static void	acsim_run_batfreed(void);
static void	acsim_pkg_oob_heap(void);

static void
acsim_vlog(const char *fmt, ...)
{
	__va_list ap;

	lockmgr(&acsim_log_lock, LK_EXCLUSIVE);
	if (acsim_log_len < sizeof(acsim_log) - 256) {
		__va_start(ap, fmt);
		kvsnprintf(acsim_log + acsim_log_len,
		    sizeof(acsim_log) - acsim_log_len, fmt, ap);
		__va_end(ap);
		acsim_log_len += strlen(acsim_log + acsim_log_len);
	}
	lockmgr(&acsim_log_lock, LK_RELEASE);
}

/* ------------------------------------------------------------------ */
/* DF-2372: AcpiOsReadPciConfiguration (1 << (bytes*8)) UB mask        */
/* ------------------------------------------------------------------ */
static void
acsim_pciub32(void)
{
	ACPI_PCI_ID id;
	ACPI_STATUS s;
	UINT64 v32, v16, v8;
	uint32_t raw;

	id.Bus = 0; id.Device = 0; id.Function = 0;

	raw = pci_cfgregread(0, 0, 0, PCIR_VENDOR, 4);
	acsim_vlog(ACSIM "DF-2372 raw pci_cfgregread(0:0.0, reg0, 4 bytes) = 0x%08x\n",
	    raw);

	s = AcpiOsReadPciConfiguration(&id, PCIR_VENDOR, &v32, 32);
	acsim_vlog(ACSIM "DF-2372 AcpiOsReadPciConfiguration Width=32 -> status 0x%x value 0x%016llx\n",
	    s, (unsigned long long)v32);
	s = AcpiOsReadPciConfiguration(&id, PCIR_VENDOR, &v16, 16);
	acsim_vlog(ACSIM "DF-2372 AcpiOsReadPciConfiguration Width=16 -> status 0x%x value 0x%016llx\n",
	    s, (unsigned long long)v16);
	s = AcpiOsReadPciConfiguration(&id, PCIR_VENDOR, &v8, 8);
	acsim_vlog(ACSIM "DF-2372 AcpiOsReadPciConfiguration Width=8  -> status 0x%x value 0x%016llx\n",
	    s, (unsigned long long)v8);

	if (raw != 0 && v32 == 0 && v16 == (raw & 0xffff))
		acsim_vlog(ACSIM "DF-2372 VERDICT: 32-bit read MASKED TO ZERO by (1<<(4*8))-1 == 0 UB shift; 16-bit read intact. BUG REPRODUCED.\n");
	else if (raw != 0 && v32 == raw)
		acsim_vlog(ACSIM "DF-2372 VERDICT: 32-bit read returned the real value (mask did not zero it) - NOT reproduced on this compiler.\n");
	else
		acsim_vlog(ACSIM "DF-2372 VERDICT: inconclusive (raw=0x%x v32=0x%llx v16=0x%llx)\n",
		    raw, (unsigned long long)v32, (unsigned long long)v16);
}

/* ------------------------------------------------------------------ */
/* DF-2385: acpi_PkgInt/acpi_PkgStr with idx == Count                  */
/* ------------------------------------------------------------------ */
static void
acsim_pkg_oob_canary(void)
{
	/*
	 * Backing store deliberately larger than Count: element [2] is
	 * past Count but inside the allocation -- exactly what an
	 * adjacent-heap-chunk read sees when its Type happens to be
	 * ACPI_TYPE_INTEGER.  If acpi_PkgInt honored Count it could
	 * NEVER return this value.
	 */
	struct {
		ACPI_OBJECT e[3];
	} *buf = kmalloc(sizeof(*buf), M_ACSIM, M_INTWAIT | M_ZERO);
	ACPI_OBJECT pkg;
	uint64_t dst = 0xdeadbeefdeadbeefULL;
	int error;

	buf->e[0].Type = ACPI_TYPE_INTEGER;
	buf->e[0].Integer.Value = 0x1111111111111111ULL;
	buf->e[1].Type = ACPI_TYPE_INTEGER;
	buf->e[1].Integer.Value = 0x2222222222222222ULL;
	/* index 2 == Count: the OOB element with a hostile Type */
	buf->e[2].Type = ACPI_TYPE_INTEGER;
	buf->e[2].Integer.Value = 0xCAFEF00DBAADC0DEULL;

	memset(&pkg, 0, sizeof(pkg));
	pkg.Type = ACPI_TYPE_PACKAGE;
	pkg.Package.Count = 2;
	pkg.Package.Elements = buf->e;

	error = acpi_PkgInt(&pkg, 0, &dst);
	acsim_vlog(ACSIM "DF-2385 acpi_PkgInt(idx=0 < Count) -> error=%d value=0x%016llx (in-bounds control)\n",
	    error, (unsigned long long)dst);

	dst = 0xdeadbeefdeadbeefULL;
	error = acpi_PkgInt(&pkg, 2, &dst);
	acsim_vlog(ACSIM "DF-2385 acpi_PkgInt(idx=2 == Count) -> error=%d value=0x%016llx\n",
	    error, (unsigned long long)dst);
	if (error == 0 && dst == 0xCAFEF00DBAADC0DEULL)
		acsim_vlog(ACSIM "DF-2385 VERDICT: OOB element read returned the canary value -- no bounds check, dead NULL guard. BUG REPRODUCED.\n");
	else
		acsim_vlog(ACSIM "DF-2385 VERDICT: idx==Count NOT satisfied from past-Count element (error=%d val=0x%llx)\n",
		    error, (unsigned long long)dst);

	kfree(buf, M_ACSIM);
}

static void
acsim_pkg_oob_page(void)
{
	/*
	 * Place the Elements array so that element [1] begins exactly at
	 * the first byte of an unmapped page: any read of it is an
	 * unambiguous kernel heap OOB read (trap 12).  Element [0]
	 * (fully inside the mapped page) is a valid control.
	 */
	vm_offset_t page;
	ACPI_OBJECT *elems;
	ACPI_OBJECT pkg;
	uint64_t dst;
	int i, ok = 0;

	for (i = 0; i < 16 && !ok; i++) {
		size_t sz = PAGE_SIZE;

		page = kmem_alloc(kernel_map, sz, VM_SUBSYS_UNKNOWN);
		if (page == 0)
			break;
		if (pmap_kextract(page + PAGE_SIZE) == 0)
			ok = 1;
		else
			kmem_free(kernel_map, page, sz);
	}
	if (!ok) {
		acsim_vlog(ACSIM "DF-2385 page-edge: no page with unmapped neighbor found (skip)\n");
		return;
	}
	acsim_vlog(ACSIM "DF-2385 page-edge: page 0x%lx, next page UNMAPPED (pmap_kextract==0)\n",
	    (unsigned long)page);

	elems = (ACPI_OBJECT *)(page + PAGE_SIZE - sizeof(ACPI_OBJECT));
	elems[0].Type = ACPI_TYPE_INTEGER;
	elems[0].Integer.Value = 0x5555555555555555ULL;

	memset(&pkg, 0, sizeof(pkg));
	pkg.Type = ACPI_TYPE_PACKAGE;
	pkg.Package.Count = 1;	/* element[1] is beyond Count AND past the page */
	pkg.Package.Elements = elems;

	dst = 0;
	(void)acpi_PkgInt(&pkg, 0, &dst);	/* control: in-page, in-count */
	acsim_vlog(ACSIM "DF-2385 page-edge control idx=0 -> 0x%016llx; now reading idx=1 (unmapped)...\n",
	    (unsigned long long)dst);
	acsim_vlog(ACSIM "DF-2385 reading idx=1: expect Fatal trap 12 here. BUG REPRODUCED.\n");

	dst = 0;
	(void)acpi_PkgInt(&pkg, 1, &dst);	/* TRAP */
	acsim_vlog(ACSIM "DF-2385 UNEXPECTED: idx=1 read did NOT trap (val=0x%llx)\n",
	    (unsigned long long)dst);
	/* not reached when the trap fires */
	kmem_free(kernel_map, page, PAGE_SIZE);
}

/* ------------------------------------------------------------------ */
/* DF-2374: ioctl hook lifetime race (dereg does not drain in-flight)  */
/* ------------------------------------------------------------------ */
static int
acsim_hook_fn(u_long cmd, caddr_t data, void *arg)
{
	atomic_add_long(&acsim_hook_calls, 1);
	/* brief residency to widen the dispatch window */
	DELAY(20);
	return (0);
}

static void
acsim_hook_churn(void *arg)
{
	/*
	 * Loop: deregister (kfree of the hp chunk) -> immediately
	 * kmalloc(32) the same zone, scribble the canary fn into it and
	 * free it (the freed chunk now carries fn=0x4141...), ->
	 * re-register.  Any dispatcher that grabbed hp before the dereg
	 * and calls hp->fn afterwards jumps to the canary.
	 */
	while (acsim_churn_run) {
		acpi_deregister_ioctl(acsim_hook_cmd, acsim_hook_fn);
		atomic_add_long(&acsim_hook_deregs, 1);
		{
			void **p = kmalloc(32, M_ACSIM, M_INTWAIT);
			int j;
			for (j = 0; j < 4; j++)
				p[j] = (void *)0x4141414141414141UL;
			kfree(p, M_ACSIM);
		}
		acpi_register_ioctl(acsim_hook_cmd, acsim_hook_fn, NULL);
		atomic_add_long(&acsim_hook_regs, 1);
		tsleep(&acsim_churn_td, 0, "churn", 1);
	}
	acsim_churn_td = NULL;
	wakeup(&acsim_churn_run);
	kthread_exit();
}

static void
acsim_hookctl(const char *sub)
{
	if (strcmp(sub, "reg") == 0) {
		acpi_register_ioctl(acsim_hook_cmd, acsim_hook_fn, NULL);
		acsim_hook_alive = 1;
		acsim_vlog(ACSIM "DF-2374 hook cmd 0x%lx registered (fn=%p)\n",
		    acsim_hook_cmd, &acsim_hook_fn);
	} else if (strcmp(sub, "dereg") == 0) {
		acpi_deregister_ioctl(acsim_hook_cmd, acsim_hook_fn);
		acsim_hook_alive = 0;
		acsim_vlog(ACSIM "DF-2374 hook deregistered\n");
	} else if (strcmp(sub, "churn_start") == 0) {
		if (acsim_churn_td == NULL) {
			acsim_churn_run = 1;
			kthread_create(acsim_hook_churn, NULL, &acsim_churn_td,
			    "acsim_churn");
			acsim_vlog(ACSIM "DF-2374 churn kthread started: dereg+canary+rereg loop; spam ioctl 0x%lx on /dev/acpi from userland now\n",
			    acsim_hook_cmd);
		}
	} else if (strcmp(sub, "churn_stop") == 0) {
		acsim_churn_run = 0;
		tsleep(&acsim_churn_run, 0, "churnst", hz);
		acsim_vlog(ACSIM "DF-2374 churn stopped: calls=%ld regs=%ld deregs=%ld\n",
		    acsim_hook_calls, acsim_hook_regs, acsim_hook_deregs);
	} else if (strcmp(sub, "stat") == 0) {
		acsim_vlog(ACSIM "DF-2374 stat: hook_alive=%d calls=%ld regs=%ld deregs=%ld\n",
		    acsim_hook_alive, acsim_hook_calls, acsim_hook_regs,
		    acsim_hook_deregs);
	}
}

/* ------------------------------------------------------------------ */
/* poison / check / deldev / eval / blocker                            */
/* ------------------------------------------------------------------ */
static void
acsim_poison(int size, int n)
{
	void **v;
	int i, j, chunks = (size + 7) / 8;

	v = kmalloc(sizeof(void *) * n, M_ACSIM, M_INTWAIT | M_ZERO);
	for (i = 0; i < n; i++) {
		v[i] = kmalloc(size, M_DEVBUF, M_INTWAIT);
		for (j = 0; j < chunks; j++)
			((uint64_t *)v[i])[j] = 0x4141414141414141ULL;
	}
	for (i = 0; i < n; i++)
		kfree(v[i], M_DEVBUF);
	kfree(v, M_ACSIM);
	acsim_vlog(ACSIM "poison: %d chunks of %d bytes sprayed with 0x41 and freed\n",
	    n, size);
}

static void
acsim_check(int size, int off)
{
	uint8_t *p = kmalloc(size, M_DEVBUF, M_INTWAIT);
	int i;
	char hex[32];

	for (i = 0; i < 8 && off + i < size + 64; i++)
		ksnprintf(hex + i * 3, 4, "%02x ", p[off + i]);
	acsim_vlog(ACSIM "check: kmalloc(%d) bytes at +%d: %s\n",
	    size, off, hex);
	kfree(p, M_DEVBUF);
}

static void
acsim_deldev(const char *dcname, int unit)
{
	devclass_t dc = devclass_find(dcname);
	device_t dev, parent;

	if (dc == NULL) {
		acsim_vlog(ACSIM "deldev: no devclass '%s'\n", dcname);
		return;
	}
	dev = devclass_get_device(dc, unit);
	if (dev == NULL) {
		acsim_vlog(ACSIM "deldev: no unit %d in '%s'\n", unit, dcname);
		return;
	}
	parent = device_get_parent(dev);
	acsim_vlog(ACSIM "deldev: deleting %s (parent %s) -- REAL detach + free\n",
	    device_get_nameunit(dev), parent ? device_get_nameunit(parent) : "?");
	if (device_delete_child(parent, dev) == 0)
		acsim_vlog(ACSIM "deldev: %s deleted\n", dcname);
	else
		acsim_vlog(ACSIM "deldev: device_delete_child FAILED\n");
}

static void
acsim_devstat(const char *dcname, int unit)
{
	devclass_t dc = devclass_find(dcname);
	device_t dev;

	if (dc == NULL) {
		acsim_vlog(ACSIM "devstat: no devclass '%s'\n", dcname);
		return;
	}
	dev = devclass_get_device(dc, unit);
	if (dev == NULL) {
		acsim_vlog(ACSIM "devstat: no unit %d in '%s'\n", unit, dcname);
		return;
	}
	acsim_vlog(ACSIM "devstat: %s state=%d busy=%d softc=%p\n",
	    device_get_nameunit(dev), (int)dev->state, dev->busy,
	    device_get_softc(dev));
}

static void
acsim_scsize(const char *dcname, int unit)
{
	devclass_t dc = devclass_find(dcname);
	device_t dev;

	if (dc == NULL) {
		acsim_vlog(ACSIM "scsize: no devclass '%s'\n", dcname);
		return;
	}
	dev = devclass_get_device(dc, unit);
	if (dev != NULL && device_get_driver(dev) != NULL) {
		acsim_vlog(ACSIM "scsize: '%s' softcsize=%d dev=%p devstate=%d busy=%d\n",
		    dcname, device_get_driver(dev)->size, dev,
		    (int)dev->state, dev->busy);
	} else {
		acsim_vlog(ACSIM "scsize: '%s' no device/driver\n", dcname);
	}
}

static void
acsim_checkmany(int size, int n)
{
	void **v = kmalloc(sizeof(void *) * n, M_ACSIM, M_INTWAIT | M_ZERO);
	int i, j, found = 0;
	int words = size / 8;

	for (i = 0; i < n; i++)
		v[i] = kmalloc(size, M_DEVBUF, M_INTWAIT);
	for (i = 0; i < n; i++) {
		uint64_t *w = v[i];
		for (j = 0; j < words; j++) {
			if (w[j] != 0x4141414141414141ULL &&
			    w[j] != 0x0000000000000000ULL) {
				acsim_vlog(ACSIM "checkmany: chunk %d offset %d (0x%03x): 0x%016llx  <-- NON-CANARY bytes in freed softc zone\n",
				    i, j * 8, j * 8,
				    (unsigned long long)w[j]);
				found++;
				if (found > 32)
					break;
			}
		}
		if (found > 32)
			break;
	}
	if (!found)
		acsim_vlog(ACSIM "checkmany: all %d chunks of %d bytes still pure canary/zero\n",
		    n, size);
	for (i = 0; i < n; i++)
		kfree(v[i], M_DEVBUF);
	kfree(v, M_ACSIM);
}


static void
acsim_eval(const char *path)
{
	ACPI_STATUS s = AcpiEvaluateObject(NULL, __DECONST(char *, path), NULL, NULL);
	acsim_vlog(ACSIM "eval %s -> status 0x%x (%s)\n", path, s,
	    ACPI_SUCCESS(s) ? "ok" : AcpiFormatException(s));
}

static void
acsim_blocker_fn(void *arg)
{
	acsim_vlog(ACSIM "blocker: running; sleeping on channel\n");
	tsleep(&acsim_log_lock, 0, "acsimblk", 30 * hz);
	acsim_vlog(ACSIM "blocker: woke up, returning (next acpi_task msg runs now)\n");
}

/* ------------------------------------------------------------------ */
/* self-driving choreographies (narrate to console via kprintf)        */
/* ------------------------------------------------------------------ */
static void
acsim_note(const char *fmt, ...)
{
	__va_list ap;
	char buf[256];

	__va_start(ap, fmt);
	kvsnprintf(buf, sizeof(buf), fmt, ap);
	__va_end(ap);
	kprintf("ACSIM-NOTE: %s", buf);
	acsim_vlog("%s", buf);
}

static int
acsim_devclass_softcsize(const char *dcname)
{
	devclass_t dc = devclass_find(dcname);
	device_t dev = (dc != NULL) ? devclass_get_device(dc, 0) : NULL;

	if (dev == NULL || device_get_driver(dev) == NULL)
		return (0);
	return (device_get_driver(dev)->size);
}

/* DF-2376(a): deferred get_bif writes into the freed softc (silent UAF
 * write capture; no panic expected) */
static void
acsim_run_ufw(void)
{
	acsim_note("DF-2376(a): eval NB1 (Notify BAT1 0x81)\n");
	acsim_eval("\\_SB.NB1");
	tsleep(&acsim_run_ufw, 0, "acsuf1", hz);
	acsim_note("DF-2376(a): poison 216-zone + deldev battery:1\n");
	acsim_poison(216, 4);
	acsim_deldev("battery", 1);
	tsleep(&acsim_run_ufw, 0, "acsuf2", 5 * hz);
	acsim_note("DF-2376(a): scanning freed 216-chunks for _BIF writes\n");
	acsim_checkmany(216, 8);
	acsim_note("DF-2376(a): done\n");
}

/* DF-2381: fujitsu deferred notify_status_changed on freed softc */
static void
acsim_run_fujitsu(void)
{
	int sz;

	acsim_note("DF-2381: queueing blocker on acpi_task\n");
	AcpiOsExecute(OSL_NOTIFY_HANDLER, acsim_blocker_fn, NULL);
	tsleep(acsim_run_fujitsu, 0, "acsfj1", hz / 2);
	acsim_note("DF-2381: eval NFJ (notify-dispatch queues behind blocker)\n");
	acsim_eval("\\_SB.NFJ");
	tsleep(acsim_run_fujitsu, 0, "acsfj2", hz / 2);
	sz = acsim_devclass_softcsize("acpi_fujitsu");
	acsim_note("DF-2381: softc size %d; poison + deldev acpi_fujitsu:0\n", sz);
	if (sz)
		acsim_poison(sz, 4);
	acsim_deldev("acpi_fujitsu", 0);
	acsim_note("DF-2381: blocker times out in 30s; deferred "
	    "notify_status_changed then runs on FREED softc -> expect trap 12\n");
	tsleep(acsim_run_fujitsu, 0, "acsfj3", 35 * hz);
	acsim_note("DF-2381: no trap after 35s (unexpected)\n");
}

/* DF-2376(b): blocker + NB2: deferred get_bif_task after delete -> NULL softc */
static void
acsim_run_batblock(void)
{
	acsim_note("DF-2376(b): blocker; eval NB2; deldev battery:2\n");
	AcpiOsExecute(OSL_NOTIFY_HANDLER, acsim_blocker_fn, NULL);
	tsleep(acsim_run_batblock, 0, "acsbb1", hz / 2);
	acsim_eval("\\_SB.NB2");
	tsleep(acsim_run_batblock, 0, "acsbb2", hz / 2);
	acsim_deldev("battery", 2);
	acsim_note("DF-2376(b): blocker times out in 30s; deferred get_bif_task "
	    "calls device_get_softc(dev) with dev->softc==NULL -> expect trap 12\n");
	tsleep(acsim_run_batblock, 0, "acsbb3", 35 * hz);
	acsim_note("DF-2376(b): no trap after 35s (unexpected)\n");
}

/* DF-2385 (cross-chunk heap OOB): the package buffer is one real
 * kmalloc chunk; Elements[idx] with idx past the end of the array lands
 * in the ADJACENT allocator chunk.  Plant a hostile ACPI_OBJECT in the
 * neighbor and show acpi_PkgInt returns its value to the caller. */
static void
acsim_pkg_oob_heap(void)
{
	ACPI_OBJECT *e, *nb, pkg;
	uint64_t dst = 0;
	int i, sz = sizeof(ACPI_OBJECT);
	int off = 2 * sz;	/* index 2 in a 1-element array: past chunk */

	e = nb = NULL;
	for (i = 0; i < 256; i++) {
		ACPI_OBJECT *a = kmalloc(sz, M_ACSIM, M_INTWAIT);
		ACPI_OBJECT *b = kmalloc(sz, M_ACSIM, M_INTWAIT);
		if ((char *)b == (char *)a + sz) {
			e = a;
			nb = b;
			kfree(b, M_ACSIM);	/* keep chunk, allow spray below */
			break;
		}
		kfree(a, M_ACSIM);
		kfree(b, M_ACSIM);
	}
	if (e == NULL) {
		acsim_vlog(ACSIM "DF-2385 heap: no adjacent chunks found (skip)\n");
		return;
	}
	/* re-allocate the neighbor chunk and plant the hostile object */
	{
		void *x;
		acsim_poison(sz, 8);
		x = kmalloc(sz, M_ACSIM, M_INTWAIT);
		if (x != nb) {
			acsim_vlog(ACSIM "DF-2385 heap: neighbor re-alloc moved (got %p want %p)\n",
			    x, nb);
			kfree(x, M_ACSIM);
			return;
		}
		memset(nb, 0, sz);
		nb->Type = ACPI_TYPE_INTEGER;
		nb->Integer.Value = 0x0BBE11E5A11CE5ULL;
	}

	memset(&pkg, 0, sizeof(pkg));
	e->Type = ACPI_TYPE_INTEGER;
	e->Integer.Value = 0x1111111111111111ULL;
	pkg.Type = ACPI_TYPE_PACKAGE;
	pkg.Package.Count = 1;
	pkg.Package.Elements = e;

	if (acpi_PkgInt(&pkg, 0, &dst) == 0)
		acsim_vlog(ACSIM "DF-2385 heap: control idx=0 -> 0x%016llx\n",
		    (unsigned long long)dst);
	dst = 0;
	if (acpi_PkgInt(&pkg, 2, &dst) == 0)
		acsim_vlog(ACSIM "DF-2385 heap: idx=2 (offset %d, past the %d-byte chunk) -> 0x%016llx = NEIGHBOR CHUNK VALUE: heap OOB read disclosure PROVEN\n",
		    off, sz, (unsigned long long)dst);
	else
		acsim_vlog(ACSIM "DF-2385 heap: idx=2 -> EINVAL (neighbor Type not INTEGER?)\n");
	kfree(e, M_ACSIM);
	kfree(nb, M_ACSIM);
}

/* DF-2381/2393/2376(b): free the softc exactly as device_set_driver()
 * does at detach time (kfree(dev->softc, M_BUS); dev->softc = NULL),
 * WITHOUT the newbus delete that deadlocks in this rig, so the queued
 * deferred callbacks run against freed memory exactly as the finding
 * describes. */
static void
acsim_free_softc(const char *dcname, int unit)
{
	devclass_t dc = devclass_find(dcname);
	device_t dev = (dc != NULL) ? devclass_get_device(dc, unit) : NULL;

	if (dev == NULL || dev->softc == NULL) {
		acsim_note("free_softc: %s:%d not found/already freed\n",
		    dcname, unit);
		return;
	}
	acsim_note("free_softc: %s:%d softc=%p size=%ld -> kfree(M_BUS)+NULL\n",
	    dcname, unit, dev->softc, (long)device_get_driver(dev)->size);
	kfree(dev->softc, M_BUS);
	dev->softc = NULL;
}

static void
acsim_run_fufreed(void)
{
	acsim_note("DF-2381: blocker; NFJ; free fujitsu softc; expect trap\n");
	AcpiOsExecute(OSL_NOTIFY_HANDLER, acsim_blocker_fn, NULL);
	tsleep(acsim_run_fufreed, 0, "acff1", hz / 2);
	acsim_eval("\\_SB.NFJ");
	tsleep(acsim_run_fufreed, 0, "acff2", hz / 2);
	acsim_poison(208, 4);	/* sizeof(acpi_fujitsu_softc) */
	acsim_free_softc("acpi_fujitsu", 0);
	acsim_note("DF-2381: softc freed+canaried; blocker times out in 30s; "
	    "deferred notify_status_changed then hits sc->dev=0x4141...\n");
	tsleep(acsim_run_fufreed, 0, "acff3", 35 * hz);
	acsim_note("DF-2381: no trap after 35s (unexpected)\n");
}

static void
acsim_run_tosfreed(void)
{
	acsim_note("DF-2393: TMD1; NTF (GHCI sleeps 2500ms); free toshiba "
	    "softc in-flight; expect trap on resume\n");
	{
		int tsz = acsim_devclass_softcsize("acpi_toshiba");
		acsim_eval("\\_SB.TMD1");
		acsim_eval("\\_SB.NTF1");
		acsim_eval("\\_SB.NTF");
		tsleep(acsim_run_tosfreed, 0, "acst1", hz / 3);
		acsim_poison(tsz, 64);
		acsim_free_softc("acpi_toshiba", 0);
		acsim_poison(tsz, 64);	/* re-spray after free */
	}
	acsim_note("DF-2393: softc freed+canaried mid-_HCI; GHCI returns at "
	    "t+2.5s -> hci_key_action derefs sc->video_handle\n");
	tsleep(acsim_run_tosfreed, 0, "acst2", 20 * hz);
	acsim_note("DF-2393: no trap after 20s (unexpected)\n");
}

static void
acsim_run_batfreed(void)
{
	acsim_note("DF-2376(b): blocker; NB2; free battery:2 softc; deferred "
	    "get_bif_task -> device_get_softc==NULL -> expect trap\n");
	AcpiOsExecute(OSL_NOTIFY_HANDLER, acsim_blocker_fn, NULL);
	tsleep(acsim_run_batfreed, 0, "acbf1", hz / 2);
	acsim_eval("\\_SB.NB2");
	tsleep(acsim_run_batfreed, 0, "acbf2", hz / 2);
	acsim_free_softc("battery", 2);
	acsim_note("DF-2376(b): softc freed; blocker out at 30s; task reads "
	    "sc->bix_present via NULL softc\n");
	tsleep(acsim_run_batfreed, 0, "acbf3", 35 * hz);
	acsim_note("DF-2376(b): no trap after 35s (unexpected)\n");
}

/* ------------------------------------------------------------------ */
/* sysctl plumbing                                                     */
/* ------------------------------------------------------------------ */
static int
acsim_op_sysctl(SYSCTL_HANDLER_ARGS)
{
	char cmd[256];
	int error, a, b;

	strlcpy(cmd, "(empty)", sizeof(cmd));
	error = sysctl_handle_string(oidp, cmd, sizeof(cmd), req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	acsim_vlog(ACSIM "op: %s\n", cmd);
	if (strcmp(cmd, "pciub32") == 0) {
		acsim_pciub32();
	} else if (strcmp(cmd, "pkg_oob_canary") == 0) {
		acsim_pkg_oob_canary();
	} else if (strcmp(cmd, "pkg_oob_page") == 0) {
		acsim_pkg_oob_page();
	} else if (strcmp(cmd, "pkg_oob_heap") == 0) {
		acsim_pkg_oob_heap();
	} else if (strcmp(cmd, "run_ufw") == 0) {
		acsim_run_ufw();
	} else if (strcmp(cmd, "run_fujitsu") == 0) {
		acsim_run_fujitsu();
	} else if (strcmp(cmd, "run_batblock") == 0) {
		acsim_run_batblock();
	} else if (strcmp(cmd, "run_fufreed") == 0) {
		acsim_run_fufreed();
	} else if (strcmp(cmd, "run_tosfreed") == 0) {
		acsim_run_tosfreed();
	} else if (strcmp(cmd, "run_batfreed") == 0) {
		acsim_run_batfreed();
	} else if (strncmp(cmd, "hook_", 5) == 0) {
		acsim_hookctl(cmd + 5);
	} else if (ksscanf(cmd, "poison:%d:%d", &a, &b) == 2) {
		acsim_poison(a, b);
	} else if (ksscanf(cmd, "check:%d:%d", &a, &b) == 2) {
		acsim_check(a, b);
	} else if (strncmp(cmd, "deldev:", 7) == 0) {
		char dc[64];
		if (ksscanf(cmd + 7, "%63[^:]:%d", dc, &a) == 2)
			acsim_deldev(dc, a);
	} else if (strncmp(cmd, "devstat:", 8) == 0) {
		char dc[64];
		if (ksscanf(cmd + 8, "%63[^:]:%d", dc, &a) == 2)
			acsim_devstat(dc, a);
	} else if (strncmp(cmd, "scsize:", 7) == 0) {
		char dc[64];
		if (ksscanf(cmd + 7, "%63[^:]:%d", dc, &a) == 2)
			acsim_scsize(dc, a);
	} else if (ksscanf(cmd, "checkmany:%d:%d", &a, &b) == 2) {
		acsim_checkmany(a, b);
	} else if (strncmp(cmd, "eval:", 5) == 0) {
		acsim_eval(cmd + 5);
	} else if (strcmp(cmd, "blockq") == 0) {
		ACPI_STATUS s = AcpiOsExecute(OSL_NOTIFY_HANDLER,
		    acsim_blocker_fn, NULL);
		acsim_vlog(ACSIM "blockq: AcpiOsExecute -> 0x%x\n", s);
	} else if (strcmp(cmd, "logreset") == 0) {
		lockmgr(&acsim_log_lock, LK_EXCLUSIVE);
		acsim_log_len = 0;
		acsim_log[0] = 0;
		lockmgr(&acsim_log_lock, LK_RELEASE);
	} else {
		acsim_vlog(ACSIM "unknown op\n");
	}
	return (0);
}

static int
acsim_log_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error;

	lockmgr(&acsim_log_lock, LK_SHARED);
	error = SYSCTL_OUT(req, acsim_log, acsim_log_len);
	lockmgr(&acsim_log_lock, LK_RELEASE);
	return (error);
}

static int
acsim_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&acsim_log_lock, "acslog", 0, 0);
		acsim_log_len = 0;
		acsim_log[0] = 0;
		acsim_vlog(ACSIM "loaded; commands via sysctl debug.acsim.op\n");
		break;
	case MOD_UNLOAD:
		return (EBUSY);
	default:
		break;
	}
	return (0);
}

static moduledata_t acsim_mod = { "acsim", acsim_modevent, NULL };
DECLARE_MODULE(acsim, acsim_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(acsim, acpi, 1, 1, 1);
