/*
 * uaudio_rig.c -- driver for the VERBATIM uaudio20_mixer_verify_desc()
 * (uaudio.c:3935-4085) verification rig (DF-2334).
 *
 * build.sh machine-extracts the production function byte-identical from
 * /usr/src (sha256-gated) into uaudio_verify.inc, plus the descriptor
 * structs it walks (uaudioreg.h) into uaudio_structs.inc.
 *
 * DF-2334 claim under test: for a UAC2 MIXER unit descriptor with
 * bLength == 5 + bNrInPins (header + source IDs, no mixer_unit_1), the
 * read of d1->bNrChannels (uaudio.c:3988) lands at offset bLength --
 * one byte past the declared descriptor body.
 *
 * Scenarios:
 *   guard  - the descriptor's last byte is the last byte of a mapped
 *            page; the next page is PROT_NONE -> the 1-byte OOB read
 *            faults with SIGSEGV at si_addr == page boundary.
 *   canary - the byte at offset bLength (physically present, e.g. the
 *            next descriptor in the walk buffer) is 0x5A.  The function
 *            reads it into `len` (visible in the rejection DPRINTF).
 *   control - well-formed mixer (bNrChannels present in-body) -> accepted,
 *            returns non-NULL.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>

/* ---------------- kernel type shims ----------------------------------- */
typedef uint8_t  uByte;
typedef uint16_t uWord;
typedef uint32_t uDWord;
#define __packed __attribute__((__packed__))

#define kprintf printf
static int uaudio_debug = 1;
#define DPRINTF(fmt, ...)	do { if (uaudio_debug)			\
    printf("UAUDIO: " fmt, ##__VA_ARGS__); } while (0)

/* usb.h:36-ish (generic descriptor header) */
struct usb_descriptor {
	uByte bLength;
	uByte bDescriptorType;
	uByte bDescriptorSubtype;
} __packed;

#define	UDESC_CS_INTERFACE	0x24

/* ---------------- VERBATIM descriptor structs (uaudioreg.h) ----------- */
#include "uaudio_structs.inc"

/* ---------------- VERBATIM production function (static, no clash) ----- */
#include "uaudio_verify.inc"

/* ---------------- scenarios ------------------------------------------- */
static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	(void)uc;
	printf("SCENARIO guard: SIGSEGV si_addr=%p  (1-byte OOB read of "
	    "d1->bNrChannels past bLength hit the guard page)\n", si->si_addr);
	fflush(stdout);
	_exit(91);
}

static long pagesz;

int
main(int argc, char **argv)
{
	const char *scen = argc == 2 ? argv[1] : "";
	uint8_t *page, *desc;
	int npins = 3;			/* bLength will be 5+3 = 8 */
	struct sigaction sa;

	pagesz = sysconf(_SC_PAGESIZE);

	if (strcmp(scen, "guard") == 0 || strcmp(scen, "canary") == 0 ||
	    strcmp(scen, "control") == 0) {
		page = mmap(NULL, 2 * pagesz, PROT_READ | PROT_WRITE,
		    MAP_PRIVATE | MAP_ANON, -1, 0);
		if (page == MAP_FAILED) { perror("mmap"); return 2; }
		if (mprotect(page + pagesz, pagesz, PROT_NONE) != 0) {
			perror("mprotect"); return 2;
		}
		memset(page, 0x00, pagesz);

		if (strcmp(scen, "control") == 0) {
			/* well-formed: bLength = len(1) + 5 + npins + 6 + 2ch */
			desc = page + pagesz - (5 + npins + 12);
			desc[0] = 5 + npins + 12;	/* == 20 */
		} else if (strcmp(scen, "canary") == 0) {
			/* crafted bLength == 5 + npins, with the OOB byte
			 * physically present IN-Page (last mapped byte) */
			desc = page + pagesz - (5 + npins + 1);
			desc[0] = 5 + npins;
		} else {
			/* crafted: bLength == 5 + npins exactly */
			desc = page + pagesz - (5 + npins);
			desc[0] = 5 + npins;
		}
		desc[1] = UDESC_CS_INTERFACE;
		desc[2] = 0x04;			/* UDESCSUB_AC_MIXER */
		desc[3] = 0x10;			/* bUnitId */
		desc[4] = npins;		/* bNrInPins */
		desc[5] = 0x01;			/* baSourceId[0] */
		desc[6] = 0x02;
		desc[7] = 0x03;
		if (strcmp(scen, "control") == 0) {
			desc[8] = 2;		/* bNrChannels (in body) */
			/* bmChannelConfig + iChannelNames + bmControls */
			memset(desc + 9, 0, 7);
		}
		if (strcmp(scen, "canary") == 0) {
			/* byte physically present just past the declared
			 * body (the next descriptor's bLength in the walk
			 * buffer): a marked value */
			desc[5 + npins] = 0x5A;
		}

		printf("SCENARIO %s: desc at page+%zd, bLength=%u, "
		    "bNrInPins=%u, d1 lands at offset %d (== bLength)\n",
		    scen, (ssize_t)(desc - page), desc[0], desc[4], 5 + npins);

		if (strcmp(scen, "guard") == 0) {
			memset(&sa, 0, sizeof(sa));
			sa.sa_sigaction = segv_handler;
			sa.sa_flags = SA_SIGINFO;
			sigaction(SIGSEGV, &sa, NULL);
			printf("SCENARIO guard: byte at offset %d is the "
			    "first byte of the PROT_NONE page -> expect "
			    "SIGSEGV now\n", 5 + npins);
			fflush(stdout);
		}

		{
			int vlen = strcmp(scen, "control") == 0 ? 1 : 0;
			const void *r = uaudio20_mixer_verify_desc(desc, vlen);
			printf("SCENARIO %s: returned %p (%s)\n", scen, r,
			    r != NULL ? "ACCEPTED" : "rejected");
			if (strcmp(scen, "canary") == 0 && r == NULL)
				printf("SCENARIO canary: rejection DPRINTF "
				    "above shows the OOB-read 0x5A inside "
				    "'len=%d of %u bytes' -- the byte past "
				    "the declared body was consumed\n",
				    5 + npins + 6 + 0x5A, desc[0]);
		}
		return 0;
	}

	fprintf(stderr, "usage: %s guard|canary|control\n", argv[0]);
	return 2;
}
