/*
 * DF-3996 — verbatim-code logic harness for the envy24(4) DMA-emulator
 * out-of-bounds index (sys/dev/sound/pci/envy24.c).
 *
 * The five emldma functions below are copied VERBATIM (only the
 * device_printf/#if(0) debug blocks and types not needed for the index
 * arithmetic are stubbed) from:
 *
 *   envy24_p32sl  sys/dev/sound/pci/envy24.c:1402-1430
 *   envy24_p16sl  sys/dev/sound/pci/envy24.c:1432-1475
 *   envy24_p8u    sys/dev/sound/pci/envy24.c:1477-1505
 *   envy24_r32sl  sys/dev/sound/pci/envy24.c:1507-1535
 *   envy24_r16sl  sys/dev/sound/pci/envy24.c:1537-1565
 *   offset math   envy24chan_trigger else-branch, envy24.c:1810-1812
 *
 * Environment model (matches envy24.c + the pcm core):
 *   - ch->size   = unit * ENVY24_SAMPLE_NUM   (envy24.c:1673)
 *   - pbuf       = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM bytes (envy24.c:2244)
 *                = 40 * 4096 = 163840 bytes = 40960 sample32_t entries
 *   - rbuf       = ENVY24_REC_BUFUNIT  * ENVY24_SAMPLE_NUM bytes (envy24.c:2245)
 *                = 48 * 4096 = 196608 bytes = 49152 sample32_t entries
 *   - readyptr   = bytes the unprivileged user wrote into the 2nd channel
 *                 before PCMTRIG_START (sndbuf_getreadyptr, [0, ch->size))
 *   - ptr        = envy24_gethwptr() frame index in [0,4095]
 *                 (envy24.c:1170-1198; hardware position of the RUNNING
 *                 first channel — observable by the user via
 *                 SNDCTL_DSP_GETOPTR on the running channel)
 *   - ch->blk    = as produced by envy24chan_setblocksize for a chosen
 *                 user blocksize (envy24.c:1744-1748)
 *
 * Two modes:
 *   soft  (default): pbuf/rbuf are placed inside a larger canary arena; after
 *          the call we report every write/read index that fell past the end
 *          of the real buffer and the value written (user PCM payload).
 *   hard: pbuf/rbuf are mmap'd with a PROT_NONE guard page immediately after
 *          the exact kernel size; the OOB access faults and the faulting
 *          address proves the index arithmetic (same arithmetic the kernel
 *          would run against its bus_dmamem_alloc'd buffer).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <signal.h>
#include <setjmp.h>

#define ENVY24_PLAY_CHNUM 10
#define ENVY24_REC_CHNUM 12
#define ENVY24_SAMPLE_NUM 4096
#define ENVY24_PLAY_BUFUNIT (4 * 10)
#define ENVY24_REC_BUFUNIT  (4 * 12)
#define PCMDIR_PLAY 1
#define PCMDIR_REC  2

typedef struct { volatile uint32_t buffer; } sample32_t;

struct sc_info;
struct sc_chinfo {
	void *buffer;
	struct sc_info *parent;
	int dir;
	unsigned num;
	uint32_t format;
	uint32_t speed;
	uint32_t blk;          /* hw block size (dword) */
	uint8_t *data;
	unsigned int size;     /* data buffer size (byte) */
	int unit;
	unsigned int offset;
	void (*emldma)(struct sc_chinfo *);
	int run;
};

struct sc_info {
	sample32_t *pbuf;
	sample32_t *rbuf;
	uint32_t psize, rsize;
};

/* ---- snd_dbuf stub: readyptr/freeptr as the pcm core reports them ---- */
static uint32_t g_readyptr;   /* bytes ready in 2nd play channel */
static uint32_t g_freeptr;    /* free ptr in 2nd rec channel */
static uint32_t g_ready;      /* ready bytes */
static uint32_t g_free;       /* free bytes */

#define sndbuf_getready(b)        (g_ready)
#define sndbuf_getfree(b)         (g_free)
#define sndbuf_getreadyptr(b)     (g_readyptr)
#define sndbuf_getfreeptr(b)      (g_freeptr)

/* ================= VERBATIM FROM envy24.c ========================= */
static void
envy24_p32sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint32_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 8;
	dmabuf = ch->parent->pbuf;
	data = (uint32_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer) / 4;
	dst = (src / 2 + ch->offset) % (ch->size / 8);
	ssize = ch->size / 4;
	dsize = ch->size / 8;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = data[src];
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = data[src + 1];
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}
}

static void
envy24_p16sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint16_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 4;
	dmabuf = ch->parent->pbuf;
	data = (uint16_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer) / 2;
	dst = (src / 2 + ch->offset) % (ch->size / 4);
	ssize = ch->size / 2;
	dsize = ch->size / 4;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = (uint32_t)data[src] << 16;
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = (uint32_t)data[src + 1] << 16;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}
}

static void
envy24_p8u(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint8_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 2;
	dmabuf = ch->parent->pbuf;
	data = (uint8_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer);
	dst = (src / 2 + ch->offset) % (ch->size / 4);
	ssize = ch->size;
	dsize = ch->size / 4;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = ((uint32_t)data[src] ^ 0x80) << 24;
		dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = ((uint32_t)data[src + 1] ^ 0x80) << 24;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}
}

static void
envy24_r32sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint32_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getfree(ch->buffer) / 8;
	dmabuf = ch->parent->rbuf;
	data = (uint32_t *)ch->data;
	dst = sndbuf_getfreeptr(ch->buffer) / 4;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 4;
	ssize = ch->size / 8;
	slot = (ch->num - 5 /*ENVY24_CHAN_REC_ADC1*/) * 2;

	for (i = 0; i < length; i++) {
		data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer;
		data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer;
		dst += 2;
		dst %= dsize;
		src++;
		src %= ssize;
	}
}

static void
envy24_r16sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint16_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getfree(ch->buffer) / 4;
	dmabuf = ch->parent->rbuf;
	data = (uint16_t *)ch->data;
	dst = sndbuf_getfreeptr(ch->buffer) / 2;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 2;
	ssize = ch->size / 8;
	slot = (ch->num - 5 /*ENVY24_CHAN_REC_ADC1*/) * 2;

	for (i = 0; i < length; i++) {
		data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer;
		data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer;
		dst += 2;
		dst %= dsize;
		src++;
		src %= ssize;
	}
}

/* VERBATIM offset computation from envy24chan_trigger (envy24.c:1810-1812) */
static unsigned int
trigger_offset(uint32_t ptr, uint32_t blk, unsigned size, int unit)
{
	return ((ptr / blk + 1) * blk % (size / 4)) * 4 / unit;
}
/* ================= END VERBATIM =================================== */

/* -------- harness arena -------- */
static const uint32_t PSIZE = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM;  /* 163840 */
static const uint32_t RSIZE = ENVY24_REC_BUFUNIT  * ENVY24_SAMPLE_NUM;  /* 196608 */
#define GUARD_BYTES (1 << 20)

static sample32_t *arena, *pbuf_real, *rbuf_real;
static uint32_t canary = 0xDEADBEEF;

static long oob_writes, oob_first_idx, oob_max_idx;


static void
check_play(const char *tag, struct sc_chinfo *ch)
{
	/* scan the guard region beyond pbuf for changes */
	long base_entries = PSIZE / 4;
	oob_writes = 0; oob_first_idx = -1; oob_max_idx = -1;
	for (long i = base_entries; i < (GUARD_BYTES / 4); i++) {
		if (arena[i].buffer != canary) {
			if (oob_writes == 0) oob_first_idx = i;
			oob_max_idx = i;
			oob_writes++;
		}
	}
	printf("[%s] pbuf entries=%ld  OOB writes past end: %ld  "
	       "first_entry=%ld (+%ld bytes past pbuf)  max_entry=%ld (+%ld bytes)\n",
	       tag, base_entries, oob_writes,
	       oob_first_idx, (oob_first_idx - base_entries) * 4,
	       oob_max_idx, (oob_max_idx - base_entries) * 4);
	if (oob_writes) {
		printf("      first OOB value = 0x%08x  (user PCM payload)\n",
		       arena[oob_first_idx].buffer);
		/* restore canary */
		for (long i = base_entries; i < (GUARD_BYTES / 4); i++)
			arena[i].buffer = canary;
	}
}

static void
run_play_case(const char *tag, int unit, unsigned hwptr, uint32_t blk,
              uint32_t ready_bytes, uint32_t readyptr, uint8_t payload)
{
	static struct sc_info sc;
	struct sc_chinfo ch;
	memset(&ch, 0, sizeof(ch));
	ch.parent = &sc;
	sc.pbuf = pbuf_real;
	ch.unit = unit;
	ch.size = unit * ENVY24_SAMPLE_NUM;
	ch.num = 1;                       /* second hw play channel (DAC1) */
	ch.blk = blk;
	ch.offset = trigger_offset(hwptr, blk, ch.size, unit);
	ch.data = calloc(1, ch.size);
	memset(ch.data, payload, ch.size);
	g_ready = ready_bytes;
	g_readyptr = readyptr;

	printf("[%s] unit=%d hwptr=%u blk=%u ready=%u readyptr=%u "
	       "-> ch->offset=%u  (dsize=%u)\n",
	       tag, unit, hwptr, blk, ready_bytes, readyptr, ch.offset,
	       ch.size / (unit == 8 ? 8 : unit == 4 ? 4 : 4));

	switch (unit) {
	case 8: ch.emldma = envy24_p32sl; break;
	case 4: ch.emldma = envy24_p16sl; break;
	case 2: ch.emldma = envy24_p8u;   break;
	}
	ch.emldma(&ch);
	check_play(tag, &ch);
	free(ch.data);
}

static long leak_reads;
static void
run_rec_case(const char *tag, int unit, unsigned hwptr, uint32_t blk,
             uint32_t free_bytes, uint32_t freeptr)
{
	static struct sc_info sc;
	struct sc_chinfo ch;
	memset(&ch, 0, sizeof(ch));
	ch.parent = &sc;
	sc.rbuf = rbuf_real;
	ch.unit = unit;
	ch.size = unit * ENVY24_SAMPLE_NUM;
	ch.num = 5;                       /* ENVY24_CHAN_REC_ADC1 */
	ch.blk = blk;
	ch.offset = trigger_offset(hwptr, blk, ch.size, unit);
	ch.data = calloc(1, ch.size);
	g_free = free_bytes;
	g_freeptr = freeptr;

	long base_entries = RSIZE / 4;
	/* poison the region past rbuf with a pattern that mimics adjacent
	 * kernel heap (in the kernel this is whatever follows the 192KB DMA
	 * allocation); we use a recognizable pattern. Only need ~2x rbuf:
	 * ssize bounds how far src can run. */
	long rbase = rbuf_real - arena;
	long nent = RSIZE / 4 + 2 * (RSIZE / 4);
	for (long i = base_entries; i < nent; i++)
		arena[rbase + i].buffer = 0x4B4B4B4B; /* "KKKK" */

	printf("[%s] unit=%d hwptr=%u blk=%u free=%u freeptr=%u "
	       "-> ch->offset=%u (ssize=%u)\n",
	       tag, unit, hwptr, blk, free_bytes, freeptr, ch.offset,
	       ch.size / 8);

	switch (unit) {
	case 8: ch.emldma = envy24_r32sl; break;
	case 4: ch.emldma = envy24_r16sl; break;
	}
	ch.emldma(&ch);

	/* scan user-visible rec data for the poison pattern => OOB read */
	leak_reads = 0;
	uint32_t *d = (uint32_t *)ch.data;
	long dn = ch.size / 4;
	for (long i = 0; i < dn; i++) {
		if (d[i] == 0x4B4B4B4B) leak_reads++;
	}
	printf("      leaked OOB kernel-heap samples copied to user: %ld\n",
	       leak_reads);
	free(ch.data);
}

/* -------- hard mode: guard page fault -------- */
static sigjmp_buf jb;
static void fault_sig(int sig, siginfo_t *si, void *uc) { siglongjmp(jb, 1); }

static void
run_hard_mode(void)
{
	size_t maplen = (PSIZE + 0x1000) & ~0xfffUL;
	uint8_t *m = mmap(NULL, maplen + 0x2000, PROT_READ|PROT_WRITE,
			  MAP_PRIVATE|MAP_ANON, -1, 0);
	mprotect(m + maplen, 0x2000, PROT_NONE);
	sample32_t *p = (sample32_t *)(m + (maplen - PSIZE)); /* buffer flush against guard */
	static struct sc_info sc; sc.pbuf = p;
	struct sc_chinfo ch; memset(&ch, 0, sizeof(ch));
	ch.parent = &sc; ch.unit = 8; ch.size = 8*ENVY24_SAMPLE_NUM; ch.num = 1;
	ch.blk = 40;
	/* hwptr chosen so offset=4095 (worst case): ((4095/40+1)*40 % 8192)/2 */
	/* documented worst case: offset = ((x % 8192) * 4 / 8) == 4095 needs
	 * x = 8190; x = (ptr/blk+1)*blk with blk=40 -> ptr = 8150 gives 8192 -> 0.
	 * Use the verbatim formula with ptr=4071: (4071/40+1)*40 = 4120 -> 2060;
	 * instead demonstrate with the formula-computed offset at hwptr=4095 */
	ch.offset = trigger_offset(4095, 40, ch.size, 8);
	ch.data = calloc(1, ch.size); memset(ch.data, 0x5A, ch.size);
	g_ready = ch.size; g_readyptr = ch.size - 4; /* src = (size-4)/4 = 8191, src/2 = 4095 */
	struct sigaction sa; memset(&sa, 0, sizeof sa);
	sa.sa_sigaction = fault_sig; sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL);
	printf("[hard] S32_LE play, offset=%u, readyptr=%u -> first write idx = %d*10+2\n",
	       ch.offset, g_readyptr, ((int)(g_readyptr/4))/2 + (int)ch.offset);
	if (sigsetjmp(jb, 1) == 0) {
		envy24_p32sl(&ch);
		printf("[hard] BUG: no fault (unexpected)\n");
	} else {
		printf("[hard] SIGFAULT at first OOB write — dmabuf[dst*10+slot] "
		       "indexed past the 163840-byte pbuf into the guard page "
		       "(kernel: adjacent heap object).\n");
	}
}

int
main(void)
{
	/* arena: [ pbuf 160KB ][ guard 1MB ][ rbuf 192KB ][ guard 1MB ] */
	uint8_t *a = calloc(1, PSIZE + GUARD_BYTES + RSIZE + GUARD_BYTES);
	arena = (sample32_t *)a;
	pbuf_real = arena;
	for (long i = 0; i < (PSIZE + GUARD_BYTES)/4; i++) arena[i].buffer = canary;
	rbuf_real = (sample32_t *)(a + PSIZE + GUARD_BYTES);
	for (long i = 0; i < (RSIZE + GUARD_BYTES)/4; i++)
		rbuf_real[i].buffer = 0x11111111;

	printf("== DF-3996 envy24 emldma OOB — verbatim driver code harness ==\n");
	printf("pbuf=%uB (%ld entries) rbuf=%uB (%ld entries)\n\n",
	       PSIZE, (long)(PSIZE/4), RSIZE, (long)(RSIZE/4));

	/* Case 1: S16_LE play (unit 4). 2nd channel starts with 1 block ready.
	 * blk from setblocksize for user blocksize 16: prev=16, blk=(16/4)*10=40 */
	run_play_case("S16 start 1blk", 4, 4095, 40, 16380 /*full-4*/, 16380, 0xA7);
	/* Case 2: S32_LE play (unit 8): blk=(16/8)*10=20; full buffer ready */
	run_play_case("S32 start full ", 8, 4095, 20, 32760, 32760, 0xB9);
	/* Case 3: U8 play (unit 2): blk=(16/2)*10=80 */
	run_play_case("U8  start full ", 2, 4095, 80, 8188, 8188, 0xC3);
	/* Case 4: small ready, offset large (offset dominates) */
	run_play_case("S16 tiny ready ", 4, 4095, 40, 4096, 4096, 0xD5);

	printf("\n-- record direction (OOB READ -> userspace leak) --\n");
	run_rec_case("S32 rec full", 8, 4095, 20, 32760, 32760);
	run_rec_case("S16 rec full", 4, 4095, 40, 16380, 16380);

	printf("\n-- hard mode (guard page) --\n");
	run_hard_mode();
	return 0;
}
