/*
 * DF-1606 - acpi_hp cmi_order[128] heap overflow PoC (userspace harness).
 *
 * Bug: sys/dev/acpica/acpi_hp/acpi_hp.c:1140-1205 (acpi_hp_hpcmi_read)
 *
 * /dev/hpcmi is created with mode 0644 (world-readable) on HP systems that
 * expose the CMI WMI GUID. Any unprivileged user can open(2) it and read(2)
 * to trigger the first-time CMI enumeration loop:
 *
 *   1164:   if (sc->cmi_order_size < 0) {
 *   1165:       maxInstance = sc->has_cmi;        <-- WMI instance count
 *   ...
 *   1171:       sc->cmi_order_size = 0;
 *   1172:       for (instance = 0; instance < maxInstance; ++instance) {
 *   1174:           if (acpi_hp_get_cmi_block(...)) { instance = maxInstance; }
 *   1180:           else {
 *   1181:               pos = sc->cmi_order_size;
 *   1182:               for (i=0; i<sc->cmi_order_size && i<127; ++i)  <-- bounded
 *   ...
 *   1190:               for (i=sc->cmi_order_size; i>pos; --i) {       <-- NOT bounded
 *   1191:                   sc->cmi_order[i].sequence = sc->cmi_order[i-1].sequence;
 *   1192:                   sc->cmi_order[i].instance  = sc->cmi_order[i-1].instance;
 *   1193:               }
 *   1198:               sc->cmi_order[pos].sequence = sequence;
 *   1200:               sc->cmi_order[pos].instance  = instance;
 *   1202:               sc->cmi_order_size++;
 *   1203:           }
 *   1204:       }
 *
 * sc->cmi_order is declared at softc line 147 as:
 *
 *   147:   struct acpi_hp_inst_seq_pair cmi_order[128];   <-- LAST field of softc
 *
 * There is NO check that cmi_order_size < 128 before writing. On the 129th
 * successful insertion the shift loop (or the final write at line 1198)
 * writes 8 bytes past cmi_order[127] - i.e. into the slab slot adjacent to
 * struct acpi_hp_softc. Each additional successful insertion adds 8 more
 * bytes of OOB write. The 8-byte payload is {uint32 sequence, uint8 instance}
 * where the values come from the WMI/BIOS CMI block (so the bytes are
 * BIOS-shaped, not attacker-shaped, but the WRITE LOCATION and TIMING are
 * 100% attacker-controlled).
 *
 * Live trigger requires HP ACPI/WMI hardware that exposes >128 CMI instances
 * (the finding cites EliteBook 840 G5/G8). This DragonFly QEMU guest has no
 * HP WMI hardware at all, so the acpi_hp driver does not attach and the
 * kernel code path is unreachable here. The harness below reproduces the
 * exact loop logic with a heap-allocated fake softc (matching the kernel's
 * kmalloc'd softc) whose cmi_order is the last field and is followed by a
 * canary region representing the adjacent kmalloc slab slot.
 *
 * Build:  cc -O2 -o df1606_poc df1606_poc.c
 * Run:    ./df1606_poc        ; exits 0 if OOB write detected
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define CMI_ORDER_N       128
#define CMI_INSTANCES     140      /* > 128 - exercises the bug (12-entry OOB) */
#define CANARY_BYTES      1024     /* generous - represents adjacent slab slot */

struct acpi_hp_inst_seq_pair {
	uint32_t sequence;
	uint8_t  instance;
	uint8_t  _pad[3];
};

/*
 * Mimic the tail of struct acpi_hp_softc. cmi_order is the LAST real field;
 * in the kernel, the softc is kmalloc'd and the bytes just past cmi_order[]
 * belong to whatever sits in the next slab slot. We replicate that layout by
 * malloc'ing (struct + canary) together and treating the trailing canary as
 * the adjacent slab slot.
 */
struct fake_softc {
	unsigned char                   header[64];
	int                             cmi_order_size;
	struct acpi_hp_inst_seq_pair    cmi_order[CMI_ORDER_N];
	/* canary region immediately follows the struct in the heap block */
};

static int fake_wmi_seq_counter = 1;

/* Mimic acpi_hp_get_cmi_block returning success and filling *sequence. */
static int
fake_get_cmi_block(uint32_t *sequence)
{
	*sequence = fake_wmi_seq_counter++;   /* vary so linear search short-circuits */
	return 0;
}

int
main(void)
{
	unsigned char *block;
	struct fake_softc *sc;
	unsigned char *NEXT_SLOT_CANARY;
	uint32_t sequence;
	uint8_t  instance;
	int maxInstance = CMI_INSTANCES;
	int pos, i, corrupted, first_off;

	block = calloc(1, sizeof(struct fake_softc) + CANARY_BYTES);
	if (block == NULL) {
		fprintf(stderr, "calloc failed\n");
		return 2;
	}
	sc = (struct fake_softc *)block;
	NEXT_SLOT_CANARY = block + sizeof(struct fake_softc);
	memset(NEXT_SLOT_CANARY, 0xAA, CANARY_BYTES);

	/* replicate acpi_hp_hpcmi_read() lines 1171-1204 exactly */
	sc->cmi_order_size = 0;
	for (instance = 0; instance < maxInstance; ++instance) {
		if (fake_get_cmi_block(&sequence)) {
			instance = maxInstance;
		} else {
			pos = sc->cmi_order_size;
			for (i = 0; i < sc->cmi_order_size && i < 127; ++i) {
				if (sc->cmi_order[i].sequence > sequence) {
					pos = i;
					break;
				}
			}
			/* shift loop has NO bound on cmi_order_size */
			for (i = sc->cmi_order_size; i > pos; --i) {
				sc->cmi_order[i].sequence = sc->cmi_order[i-1].sequence;
				sc->cmi_order[i].instance = sc->cmi_order[i-1].instance;
			}
			sc->cmi_order[pos].sequence = sequence;
			sc->cmi_order[pos].instance = instance;
			sc->cmi_order_size++;
		}
	}

	corrupted = 0;
	first_off = -1;
	for (i = 0; i < CANARY_BYTES; ++i) {
		if (NEXT_SLOT_CANARY[i] != 0xAA) {
			if (first_off < 0) first_off = i;
			corrupted++;
		}
	}

	printf("maxInstance           = %d\n", maxInstance);
	printf("cmi_order array bound = %d entries (%d bytes)\n",
	    CMI_ORDER_N, (int)(CMI_ORDER_N * sizeof(struct acpi_hp_inst_seq_pair)));
	printf("cmi_order_size after  = %d entries (overflow by %d entries)\n",
	    sc->cmi_order_size, sc->cmi_order_size - CMI_ORDER_N);
	printf("adjacent-slab canary corrupted bytes = %d (first at +%d)\n",
	    corrupted, first_off);

	if (corrupted > 0) {
		printf("[OK] heap OOB write past cmi_order[127] confirmed: %d bytes\n",
		    corrupted);
		printf("     kernel path: acpi_hp_hpcmi_read lines 1190-1202\n");
		printf("     in-kernel effect: corruption of adjacent kmalloc-2048 slab\n");
		printf("     slot -> INVARIANTS panic on default GENERIC; silent heap\n");
		printf("     corruption on noinv.\n");
		free(block);
		return 0;
	}
	printf("[FAIL] no OOB write detected.\n");
	free(block);
	return 1;
}
