/* SPDX-License-Identifier: BSD-2-Clause
 * DF-1076 — faithful userspace harness for the ichsmb block-read OOB write.
 *
 * The vulnerable path (sys/bus/smbus/ichsmb/ichsmb.c) is reachable ONLY when
 * an ichsmb PCI SMBus controller is present AND a malicious SMBus/I2C slave
 * is electrically on the bus. The audit QEMU guest has NO ichsmb PCI device
 * (PCI 0:1:3 is the PIIX4 *ACPI* function, not the SMBus function — class
 * 0x068000 not 0x0c05), so the bug cannot be triggered dynamically here.
 *
 * This harness reproduces the *exact algorithmic logic* of the vulnerable
 * ISR block-read branch so the primitive can be demonstrated and the fix
 * validated without malicious hardware. It:
 *
 *   1. Allocates a struct laid out byte-for-byte like `struct ichsmb_softc`
 *      (per sys/bus/smbus/ichsmb/ichsmb_var.h:47-66), so `block_data[32]`
 *      is immediately followed by `struct lock mutex` and a poison canary.
 *   2. Implements `bus_read_1()` as a controllable stub that returns the
 *      slave-supplied count (255) and payload byte (0xAA) — exactly what a
 *      malicious peripheral would put on the wire.
 *   3. Drives the same loop the ISR drives (ichsmb.c:574-585), calling the
 *      stub once per BYTE_DONE_STS, until block_index reaches block_count.
 *
 * Run twice: once with CLAMP=0 (the unpatched kernel — corruption expected),
 * once with CLAMP=1 (the patched kernel — clamp at ichsmb.c:575 prevents the
 * overflow). The harness compares the canary bytes adjacent to block_data[]
 * in each case to make the primitive and its closure visible.
 *
 * Build:  cc -O0 -o harness harness.c
 *   or:   cc -O0 -DCLAMP_FIX -o harness_clamped harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---- Mirror sys/bus/smbus/ichsmb/ichsmb_var.h:47-66 layout exactly ---- */
struct lock {            /* stand-in; kernel's struct lock is larger but the
                          * harness only needs the *adjacency* — what matters
                          * is that overflow past block_data[32] lands in
                          * something the kernel cares about. */
    char payload[64];
};

struct ichsmb_softc {
    /* Device/bus stuff (pointers — sizes don't matter for adjacency) */
    void *dev, *smb, *io_res;
    int  io_rid;
    void *irq_res;
    int  irq_rid;
    void *irq_handle;
    /* Device state */
    int  ich_cmd;
    int  smb_error;
    int  block_count;          /* ichsmb_var.h:61 */
    int  block_index;          /* ichsmb_var.h:62 */
    uint8_t block_write;       /* ichsmb_var.h:63 */
    uint8_t block_data[32];    /* ichsmb_var.h:64 — the fixed-size buffer */
    struct lock mutex;         /* ichsmb_var.h:65 — first victim of OOB */
    /* Harness canary: anything that writes past block_data lands here. */
    uint8_t canary[192];
};

/* ICH register numbers that the ISR reads from. */
#define ICH_D0          0x05    /* count byte supplied by the slave        */
#define ICH_BLOCK_DB    0x07    /* next data byte supplied by the slave    */

/*
 * bus_read_1 stand-in. The harness drives this so that the FIRST read of
 * ICH_D0 returns 255 (the slave's "I'm sending 255 bytes" lie) and any
 * read of ICH_BLOCK_DB returns PAYLOAD (0xAA — the attacker's bytes).
 *
 * On real hardware this byte stream is exactly what comes back from a
 * malicious SMBus slave or a malicious DIMM SPD EEPROM.
 */
#define COUNT_BYTE   255
#define PAYLOAD_BYTE 0xAA

static uint8_t
bus_read_1(int reg)
{
    switch (reg) {
    case ICH_D0:       return COUNT_BYTE;
    case ICH_BLOCK_DB: return PAYLOAD_BYTE;
    default:           return 0;
    }
}

/*
 * Reproduce ichsmb_device_intr block-read branch (ichsmb.c:571-597).
 * We model only the BYTE_DONE_STS path: each call processes one byte,
 * incrementing block_index until it reaches block_count, exactly as the
 * real ISR does across multiple interrupts.
 */
static void
block_read_isr_step(struct ichsmb_softc *sc)
{
    /* ichsmb.c:573-577 — "First interrupt, get the count also" */
    if (sc->block_index == 0) {
        sc->block_count = bus_read_1(ICH_D0);
#ifdef CLAMP_FIX
        /* The fix: clamp slave-supplied count to sizeof(block_data). */
        if (sc->block_count > (int)sizeof(sc->block_data))
            sc->block_count = sizeof(sc->block_data);
#endif
    }

    /* ichsmb.c:579-585 — "Get next byte, if any" + indexed write */
    if (sc->block_index < sc->block_count) {
        sc->block_data[sc->block_index++] = bus_read_1(ICH_BLOCK_DB);
        /* (LAST_BYTE handling at ichsmb.c:587-596 omitted — it only
         * touches bus_write_1, not block_data, so it can't affect the
         * primitive.) */
    }
}

int
main(void)
{
    struct ichsmb_softc *sc = calloc(1, sizeof(*sc));

    /* Simulate ichsmb_bread() setup (ichsmb.c:424-429):
     *   - user-supplied *count was validated to be in [1,32];
     *   - sc->block_count is ZEROED here (so the user bound does NOT
     *     constrain the slave-supplied value the ISR will read later). */
    sc->block_count = 0;       /* ichsmb.c:427 */
    sc->block_index = 0;       /* ichsmb.c:428 */
    sc->block_write = 0;       /* ichsmb.c:429 — read direction */

    /* Poison the canary so we can detect any OOB write. */
    memset(sc->canary, 0xCC, sizeof(sc->canary));
    memset(sc->mutex.payload, 0xDD, sizeof(sc->mutex.payload));

    printf("sizeof(ichsmb_softc) = %zu\n", sizeof(*sc));
    printf("offsetof(block_data)  = %zu\n",
        (size_t)((uint8_t*)sc->block_data - (uint8_t*)sc));
    printf("offsetof(mutex)       = %zu (first byte AFTER block_data[31])\n",
        (size_t)((uint8_t*)&sc->mutex - (uint8_t*)sc));
    printf("offsetof(canary)      = %zu\n",
        (size_t)((uint8_t*)sc->canary - (uint8_t*)sc));

#ifdef CLAMP_FIX
    printf("\n[CLAMP_FIX enabled — modeling the patched kernel]\n");
#else
    printf("\n[CLAMP_FIX disabled — modeling the unpatched audit-source kernel]\n");
#endif

    printf("slave-supplied count byte (ICH_D0) = %u (>32 = %s)\n",
        (unsigned)COUNT_BYTE, COUNT_BYTE > 32 ? "YES — OOB" : "no");
    printf("payload byte (ICH_BLOCK_DB)        = 0x%02x\n", PAYLOAD_BYTE);

    /* Drive the ISR for up to 256 byte-done events. */
    int steps = 0;
    int prev_index = -1;
    while (sc->block_index != prev_index && steps < 300) {
        prev_index = sc->block_index;
        block_read_isr_step(sc);
        steps++;
    }

    printf("\npost-ISR block_count = %d\n", sc->block_count);
    printf("post-ISR block_index = %d (== number of bytes written)\n",
        sc->block_index);

    /* Diagnostic: dump the first 8 bytes of each region so we can SEE
     * what landed where. */
    printf("\n-- hex dump --\n");
    printf("block_data[0..7]  :");
    for (int i = 0; i < 8; i++) printf(" %02x", sc->block_data[i]);
    printf("\nblock_data[24..31]:");
    for (int i = 24; i < 32; i++) printf(" %02x", sc->block_data[i]);
    printf("\nmutex.payload[0..7]   :");
    for (int i = 0; i < 8; i++) printf(" %02x", (uint8_t)sc->mutex.payload[i]);
    printf("\ncanary[0..7]      :");
    for (int i = 0; i < 8; i++) printf(" %02x", sc->canary[i]);
    printf("\ncanary[151..159]  :");
    for (int i = 151; i < 160 && i < (int)sizeof(sc->canary); i++)
        printf(" %02x", sc->canary[i]);
    printf("\n");

    /* Tally corruption: how many bytes after block_data[31] are
     * no longer the poison value? Cast to uint8_t — `char` is signed
     * on this platform and 0xAA would sign-extend. */
    int mutex_corrupt = 0;
    for (size_t i = 0; i < sizeof(sc->mutex.payload); i++)
        if ((uint8_t)sc->mutex.payload[i] != 0xDD) mutex_corrupt++;
    int canary_corrupt = 0;
    for (size_t i = 0; i < sizeof(sc->canary); i++)
        if ((uint8_t)sc->canary[i] != 0xCC) canary_corrupt++;

    printf("bytes corrupted inside struct lock mutex = %d / %zu\n",
        mutex_corrupt, sizeof(sc->mutex.payload));
    printf("bytes corrupted in trailing canary       = %d / %zu\n",
        canary_corrupt, sizeof(sc->canary));

    /* Direct bounds check: how many bytes of block_data, mutex.payload,
     * and canary equal the payload (0xAA) vs the original poison? */
    int bd_payload = 0, mtx_payload = 0, can_payload = 0;
    for (size_t i = 0; i < sizeof(sc->block_data); i++)
        if ((uint8_t)sc->block_data[i] == PAYLOAD_BYTE) bd_payload++;
    for (size_t i = 0; i < sizeof(sc->mutex.payload); i++)
        if ((uint8_t)sc->mutex.payload[i] == PAYLOAD_BYTE) mtx_payload++;
    for (size_t i = 0; i < sizeof(sc->canary); i++)
        if ((uint8_t)sc->canary[i] == PAYLOAD_BYTE) can_payload++;

    printf("\npayload-bytes (0xAA) found in block_data[32] : %d / 32\n", bd_payload);
    printf("payload-bytes (0xAA) found in mutex.payload[64]: %d / 64\n", mtx_payload);
    printf("payload-bytes (0xAA) found in canary[192]      : %d / 192\n", can_payload);

    if (mtx_payload == 0 && can_payload == 0) {
        printf("\nVERDICT: NO OVERFLOW — writes stayed inside block_data[32].\n");
        return 0;
    }
    printf("\nVERDICT: OOB WRITE CONFIRMED — %d payload-bytes landed past "
           "block_data[31] (%d in mutex, %d in canary) — primitive matches "
           "the ichsmb.c:583 indexed write driven by the unclamped slave "
           "count read at ichsmb.c:575.\n",
        mtx_payload + can_payload, mtx_payload, can_payload);
    printf("On a live kernel, corrupting struct lock mutex causes an "
           "immediate panic on the next lockmgr op (ichsmb.c:615).\n");
    return 0;
}
