/*
 * DF-3998 / DF-3999 — verbatim-code logic harness for the envy24ht(4)
 * DMA-emulator out-of-bounds indices (sys/dev/sound/pci/envy24ht.c).
 *
 * The five emldma functions below are copied VERBATIM (only #if(0) debug
 * blocks omitted) from:
 *
 *   envy24ht_p32sl  sys/dev/sound/pci/envy24ht.c:1311-1339
 *   envy24ht_p16sl  sys/dev/sound/pci/envy24ht.c:1341-1384
 *   envy24ht_p8u    sys/dev/sound/pci/envy24ht.c:1386-1414
 *   envy24ht_r32sl  sys/dev/sound/pci/envy24ht.c:1416-1444
 *   envy24ht_r16sl  sys/dev/sound/pci/envy24ht.c:1446-1474
 *   offset math     envy24htchan_trigger else-branch, envy24ht.c:1721-1723
 *   hwptr math      envy24ht_gethwptr, envy24ht.c:1124-1152
 *   channel map     envy24ht_chanmap[], envy24ht.c:221-233
 *   registration    envy24ht_pci_attach, envy24ht.c:2494-2505
 *                   (1 play channel + (2+adcn) rec channels; adcn =
 *                    ((scfg & 0x0c)>>2)+1; standard board scfg=0x0b ->
 *                    adcn=3 -> 5 rec channels, hw nums {1,2,3,4,10} ->
 *                    slot=(num-5)*2 = {-8,-6,-4,-2,+10})
 *
 * Environment model (matches envy24ht.c + the pcm core):
 *   - pbuf = ENVY24HT_PLAY_BUFUNIT(32) * ENVY24HT_SAMPLE_NUM(4096)
 *          = 131072 bytes = 32768 sample32_t entries      (envy24ht.c:2160)
 *   - rbuf = ENVY24HT_REC_BUFUNIT(8) * 4096 = 32768 bytes
 *          = 8192 sample32_t entries                      (envy24ht.c:2161)
 *   - ch->size = unit * ENVY24HT_SAMPLE_NUM               (envy24ht.c:1584)
 *   - readyptr/freeptr in [0, ch->size) — controlled by how many bytes the
 *     unprivileged user wrote/read on the channel before triggering
 *   - ptr = envy24ht_gethwptr() frame index in [0,4095] of the RUNNING
 *     first same-direction channel — user-observable via
 *     SNDCTL_DSP_GETOPTR/GETIPTR on that channel
 *   - ch->blk = as produced by envy24htchan_setblocksize
 *     (envy24ht.c:1648-1659) for a user-chosen blocksize:
 *       rec S16:  blk = prev/4*2 = prev/2   (prev = power of 2 <= size/2)
 *       play S16: blk = prev/4*8 = prev*2
 *
 * Cases (soft mode; canary arenas sized around the exact kernel buffers):
 *   [A] DF-3999: FIRST record channel (hw num 1 -> slot -8), offset 0:
 *       EMLDMARD reads sample32_t entries at indices -8..-1 (32 bytes
 *       BEFORE rbuf) into the user record stream.
 *   [B] DF-3999: 5th record channel (hw num 10 -> slot +10), offset 0:
 *       reads up to 10 entries PAST the rbuf end, unconditionally.
 *   [C] DF-3998: SECOND record channel START (S16_LE, attacker-chosen
 *       ptr/blk -> offset 4088): first loop access reads ~32 KB past rbuf
 *       into the user record stream (kernel heap disclosure).
 *   [D] DF-3998 play twin (p16sl): same unclamped dst=src/2+offset writes
 *       attacker PCM up to ~130 KB past pbuf — write primitive exists but
 *       needs 2 running PLAY channels; the driver registers exactly 1
 *       (envy24ht.c:2494-2499) so on the driver as shipped this is LATENT
 *       (goes live if the commented-out 5-play-channel loop is restored).
 *
 * hard mode: rbuf against a PROT_NONE guard page sized exactly like the
 * kernel bus_dmamem allocation -> case [C] faults, proving the index
 * arithmetic the kernel would run against real kernel memory.
 *
 * Kernel-heap stand-in markers (chosen so the leaked value survives the
 * 16-bit truncation of r16sl):
 *   before rbuf: 0xABADBE00 | (i & 0xff)   -> 32-bit 0xABADBExx / 16-bit 0xBExx
 *   past rbuf:   0xDEADFE00 | (i & 0xff)   -> 32-bit 0xDEADFExx / 16-bit 0xFExx
 *   past pbuf:   0xDEAD0000 | i            (overwritten value 0xB9B90000)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <signal.h>
#include <setjmp.h>

#define ENVY24HT_PLAY_CHNUM 8
#define ENVY24HT_REC_CHNUM 2
#define ENVY24HT_SAMPLE_NUM 4096
#define ENVY24HT_PLAY_BUFUNIT (4 * 8)
#define ENVY24HT_REC_BUFUNIT  (4 * 2)
#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;
	uint8_t *data;
	unsigned int size;
	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;
};

static uint32_t g_readyptr, g_freeptr, g_ready, g_free;
#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 envy24ht.c ========================= */
static void
envy24ht_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 % (ch->size / 4);
	src -= src % 2;
	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 * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src];
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1];
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_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 % (ch->size / 2);
	src -= src % 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 * ENVY24HT_PLAY_CHNUM + slot].buffer = (uint32_t)data[src] << 16;
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (uint32_t)data[src + 1] << 16;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_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) % ch->size;
	src -= src % 2;
	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 * ENVY24HT_PLAY_CHNUM + slot].buffer = ((uint32_t)data[src] ^ 0x80) << 24;
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((uint32_t)data[src + 1] ^ 0x80) << 24;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_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 % (ch->size / 4);
	dst -= dst % 2;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 4;
	ssize = ch->size / 8;
	slot = ((ch->num - 5 /* ENVY24HT_CHAN_REC_ADC1 */) * 2) % ENVY24HT_REC_CHNUM;

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

	return;
}

static void
envy24ht_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 % (ch->size / 2);
	dst -= dst % 2;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 2;
	ssize = ch->size / 8;
	slot = ((ch->num - 5 /* ENVY24HT_CHAN_REC_ADC1 */) * 2) % ENVY24HT_REC_CHNUM;

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

	return;
}

/* envy24htchan_trigger PCMTRIG_START, second same-direction channel
 * (envy24ht.c:1714-1726, the sc->run[slot] != 1 branch) */
static unsigned int
trigger_offset_2nd_channel(uint32_t hw_ptr /* frames */, uint32_t blk,
                           uint32_t chsize_dwords /* ch->size/4 */, int unit)
{
	return ((hw_ptr / blk + 1) * blk % chsize_dwords) * 4 / unit;
}

/* ================== harness scaffolding ============================ */
static const size_t PBUF_BYTES = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM;
static const size_t RBUF_BYTES = ENVY24HT_REC_BUFUNIT * ENVY24HT_SAMPLE_NUM;
static const size_t GUARD = 0x40000;

static char *arena_p, *arena_r;
static sample32_t *pbuf, *rbuf;
static sigjmp_buf jb;
static volatile int in_test;

static void segv(int sig) { if (in_test) siglongjmp(jb, 1); else _exit(9); }

static void
setup_soft(void)
{
	/* pbuf and rbuf live in SEPARATE arenas, each with canary margins on
	 * both sides — in the kernel they are separate bus_dmamem_alloc's
	 * (envy24ht.c:2171-2187), so adjacent canaries must not alias. */
	arena_p = malloc(PBUF_BYTES + 2 * GUARD);
	arena_r = malloc(RBUF_BYTES + 2 * GUARD);
	memset(arena_p, 0, PBUF_BYTES + 2 * GUARD);
	memset(arena_r, 0, RBUF_BYTES + 2 * GUARD);
	pbuf = (sample32_t *)(arena_p + GUARD);
	rbuf = (sample32_t *)(arena_r + GUARD);
	/* kernel-heap stand-ins around each buffer */
	for (size_t i = 1; i <= GUARD / 4; i++) {
		pbuf[-(ssize_t)i].buffer = 0xABADBE00u | (i & 0xffu);
		rbuf[-(ssize_t)i].buffer = 0xABADBE00u | (i & 0xffu);
	}
	for (size_t i = 0; i < GUARD / 4; i++) {
		pbuf[PBUF_BYTES / 4 + i].buffer = 0xDEAD0000u + (uint32_t)i;
		rbuf[RBUF_BYTES / 4 + i].buffer = 0xDEADFE00u | (i & 0xffu);
	}
}

static struct sc_info S;

static void
report_rec32(const char *tag, uint32_t *data32, size_t nwords)
{
	size_t before = 0, past = 0;
	uint32_t firstv = 0; size_t firsti = 0; int have = 0;
	for (size_t i = 0; i < nwords; i++) {
		uint32_t v = data32[i];
		if ((v & 0xFF00FF00u) == 0xAB00BE00u ||
		    (v & 0xFF00FF00u) == 0xDE00FE00u) {
			if ((v & 0xFF00FF00u) == 0xAB00BE00u) before++; else past++;
			if (!have) { firstv = v; firsti = i; have = 1; }
		}
	}
	printf("  [%s] user stream received %zu samples from BEFORE rbuf, "
	       "%zu from PAST rbuf end%s", tag, before, past,
	       have ? "\n" : " (none)\n");
	if (have)
		printf("         first leaked: data[%zu] = 0x%08x (kernel-heap marker)\n",
		       firsti, firstv);
}

static void
report_rec16(const char *tag, uint16_t *data16, size_t nwords)
{
	size_t before = 0, past = 0;
	uint16_t firstv = 0; size_t firsti = 0; int have = 0;
	for (size_t i = 0; i < nwords; i++) {
		uint16_t v = data16[i];
		if ((v & 0xFF00u) == 0xBE00u || (v & 0xFF00u) == 0xFE00u) {
			if ((v & 0xFF00u) == 0xBE00u) before++; else past++;
			if (!have) { firstv = v; firsti = i; have = 1; }
		}
	}
	printf("  [%s] user stream received %zu samples from BEFORE rbuf, "
	       "%zu from PAST rbuf end%s", tag, before, past,
	       have ? "\n" : " (none)\n");
	if (have)
		printf("         first leaked: data[%zu] = 0x%04x (low half of "
		       "kernel-heap 0xDEAD/0xABAD marker word)\n", firsti, firstv);
}

int main(int argc, char **argv)
{
	int hard = (argc > 1 && strcmp(argv[1], "hard") == 0);

	printf("DF-3998/DF-3999 FIXED variant (mode=%s)\n",
	       hard ? "hard" : "soft");
	printf("pbuf=%zuB (%zu entries)  rbuf=%zuB (%zu entries)\n",
	       PBUF_BYTES, PBUF_BYTES / 4, RBUF_BYTES, RBUF_BYTES / 4);
	signal(SIGSEGV, segv);

	if (!hard) {
		setup_soft();
		S.pbuf = pbuf; S.rbuf = rbuf;
		S.psize = PBUF_BYTES; S.rsize = RBUF_BYTES;

		/* [A] DF-3999 — first rec channel, hw num 1, slot -8, offset 0 */
		{
			struct sc_chinfo ch;
			static uint32_t data32[8192 + 4];
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 1;
			ch.unit = 8; ch.size = 8 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data32;
			memset(data32, 0, sizeof(data32));
			g_freeptr = 0; g_free = ch.size;
			envy24ht_r32sl(&ch);
			printf("[A] 1st rec ch (num=1, slot=-8, offset=0), r32sl:\n");
			report_rec32("A", data32, 32 /* first 16 frames */);
		}

		/* [B] DF-3999 — 5th rec channel, hw num 10, slot +10, offset 0 */
		{
			struct sc_chinfo ch;
			static uint32_t data32[8192 + 4];
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 10;
			ch.unit = 8; ch.size = 8 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data32;
			memset(data32, 0, sizeof(data32));
			g_freeptr = 0; g_free = ch.size;
			envy24ht_r32sl(&ch);
			printf("[B] 5th rec ch (num=10, slot=+10, offset=0), r32sl:\n");
			report_rec32("B", data32, 8192);
		}

		/* [C] DF-3998 — second rec channel START, S16_LE, worst case:
		 *     user blocksize 16 -> blk = 8 dwords; running 1st rec
		 *     channel at hw ptr 4087 frames (via GETIPTR);
		 *     offset = ((4087/8)+1)*8 % 4096 = 4088.
		 *     dst = freeptr/2 = 8191 -> the first access also writes
		 *     data16[8192], one element past the 8192-entry ch->data
		 *     (kernel M_ENVY24HT kmalloc) - same access-before-wrap
		 *     defect on the data side. */
		{
			struct sc_chinfo ch;
			static uint16_t data16[16384];
			uint32_t blk = 8, off;
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 2;
			ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data16;
			memset(data16, 0, sizeof(data16));
			off = trigger_offset_2nd_channel(4087, blk,
			                                 ch.size / 4, ch.unit);
			ch.offset = off;
			printf("[C] 2nd rec ch (num=2, slot=-6) START: ptr=4087 "
			       "blk=%u -> offset=%u (ssize=%d)\n",
			       blk, off, ch.size / 8);
			g_freeptr = ch.size - 2; g_free = ch.size;
			envy24ht_r16sl(&ch);
			report_rec16("C", data16, 8192);
			if (data16[8192] != 0)
				printf("         data-side OOB write: data16[8192] = "
				       "0x%04x (one element past ch->data)\n",
				       data16[8192]);
		}

		/* [D] DF-3998 play twin — p16sl with offset (LATENT on this
		 *     driver as registered: needs 2 running play channels).
		 *     user blocksize 16 -> prev=16 -> blk = 16/4*8 = 32 dwords;
		 *     ptr=4063 -> offset = ((4063/32)+1)*32 % 4096 = 4064 */
		{
			struct sc_chinfo ch;
			static uint16_t data16[8192];
			uint32_t blk = 32, off;   /* prev=16 -> 16/4*8 = 32 */
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_PLAY; ch.num = 1;
			ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data16;
			for (size_t i = 0; i < 8192; i++)
				data16[i] = 0xB9B9;
			off = trigger_offset_2nd_channel(4063, blk,
			                                 ch.size / 4, ch.unit);
			ch.offset = off;
			g_readyptr = ch.size - 4; g_ready = ch.size;
			envy24ht_p16sl(&ch);
			size_t oob = 0, firsti = 0; int have = 0;
			for (size_t i = 0; i < GUARD / 4; i++) {
				if (pbuf[PBUF_BYTES / 4 + i].buffer !=
				    0xDEAD0000u + (uint32_t)i) {
					oob++;
					if (!have) { firsti = i; have = 1; }
				}
			}
			printf("[D] play twin p16sl (LATENT, 2nd play ch): "
			       "offset=%u -> %zu sample32 entries written past "
			       "pbuf end", off, oob);
			if (have)
				printf(", first at +%zu bytes (value 0x%08x)\n",
				       firsti * 4,
				       (unsigned)pbuf[PBUF_BYTES / 4 + firsti].buffer);
			else
				printf(" none\n");
		}
		free(arena_p);
		free(arena_r);
		printf("SOFT-DONE\n");
		return 0;
	}

	/* hard mode: guard page exactly after rbuf */
	{
		size_t pagesz = 4096;
		char *m = mmap(NULL, RBUF_BYTES + pagesz, PROT_READ | PROT_WRITE,
		               MAP_PRIVATE | MAP_ANON, -1, 0);
		if (m == MAP_FAILED) { perror("mmap"); return 1; }
		if (mprotect(m + RBUF_BYTES, pagesz, PROT_NONE) != 0) {
			perror("mprotect"); return 1;
		}
		memset(m, 0, RBUF_BYTES);
		S.rbuf = (sample32_t *)m; S.pbuf = NULL;
		S.psize = PBUF_BYTES; S.rsize = RBUF_BYTES;

		struct sc_chinfo ch;
		static uint16_t data16[8192];
		memset(&ch, 0, sizeof(ch));
		ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 2;
		ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
		ch.data = (uint8_t *)data16;
		ch.offset = trigger_offset_2nd_channel(4087, 8,
		                                       ch.size / 4, ch.unit);
		g_freeptr = ch.size - 2; g_free = ch.size;
		in_test = 1;
		if (sigsetjmp(jb, 1) == 0) {
			envy24ht_r16sl(&ch);
			printf("HARD: no fault (unexpected)\n");
		} else {
			printf("HARD: SIGSEGV at first OOB read past rbuf end "
			       "(offset=%u) - index arithmetic proven\n",
			       ch.offset);
		}
		in_test = 0;
		munmap(m, RBUF_BYTES + pagesz);
	}
	return 0;
}
