/*
 * DF-1172 harness: ata_raid_lsiv2_read_meta OOB write (static BSS + heap)
 *
 * Replicates the three OOB sites in ata_raid_lsiv2_read_meta()
 * (sys/dev/disk/nata/ata-raid.c:2799-2869) using FAITHFUL copies of the real
 * kernel structs (ata-raid.h:50 struct ar_softc, ata-raid.h:459 struct
 * lsiv2_raid_conf, ata-raid.c:138 static ata_raid_arrays[MAX_ARRAYS]).
 *
 *   ata-raid.h:37   #define MAX_ARRAYS  16
 *   ata-raid.h:39   #define MAX_DISKS   16
 *   ata-raid.c:138  static struct ar_softc *ata_raid_arrays[MAX_ARRAYS];
 *   ata-raid.h:50   struct ar_softc { ...; struct ar_disk disks[MAX_DISKS]; ... };
 *   ata-raid.h:459  struct lsiv2_raid_conf { ...; union { raid; disk; } configs[30];
 *                    u_int8_t disk_number; u_int8_t raid_number; ... };
 *
 * BUG 1 (BSS OOB write of a heap pointer):
 *   ata-raid.c:2802  if (!raidp[array + meta->raid_number]) {
 *   ata-raid.c:2803      raidp[array + meta->raid_number] = kmalloc(sizeof(ar_softc),...);
 *   meta->raid_number is u_int8_t (0..255). raidp=ata_raid_arrays[16].
 *   For meta->raid_number >= 16, index (array + raid_number) >= 16 writes a
 *   kmalloc'd heap pointer into BSS past ata_raid_arrays[].
 *
 * BUG 2 (OOB read of configs[]):
 *   ata-raid.c:2818  raid_entry = meta->raid_number;            // up to 255
 *   ata-raid.c:2819/2822  meta->configs[raid_entry] ...          // configs[30]
 *   raid_entry >= 30 reads past the configs[] union array.
 *
 * BUG 3 (heap OOB write into ar_softc):
 *   ata-raid.c:2866  raid->disks[meta->disk_number].dev     = parent;
 *   ata-raid.c:2867  raid->disks[meta->disk_number].sectors = meta->...disk_sectors;
 *   ata-raid.c:2869  raid->disks[meta->disk_number].flags   = ...;
 *   meta->disk_number is u_int8_t (0..255); disks[MAX_DISKS=16].
 *   disk_number >= 16 writes into the trailing ar_softc fields (toggle,
 *   rebuild_lba, lock, disk, devstat, cdev, pid) and past the heap allocation.
 *
 * Driver IS compiled into the default X86_64_GENERIC kernel (device nataraid,
 * X86_64_GENERIC:83) and the guest's Intel PIIX4 IDE controller passes the
 * read_metadata vendor gate. The live in-kernel trigger requires an ATA disk
 * carrying crafted LSI v2 metadata; the QEMU/loader artifact documented in
 * DF-1171 prevents adding a disk on this guest, so this harness proves the
 * primitive at the indexing level (never performs the OOB access).
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#define MAX_ARRAYS 16
#define MAX_DISKS  16

/* faithful ar_disk (ata-raid.h:97) */
typedef unsigned long device_t;           /* kernel pointer width */
struct ar_disk {
    device_t   dev;        /* 8 */
    uint8_t    serial[16]; /* 16 */
    uint64_t   sectors;    /* 8 */
    int64_t    last_lba;   /* 8 (off_t) */
    unsigned   flags;      /* 4 */
    unsigned   pad;        /* 4 -> sizeof 48 */
};

/* faithful prefix of ar_softc up to and including disks[] (ata-raid.h:50) */
struct ar_softc_prefix {
    int       lun;            /* 4 */
    uint8_t   name[32];       /* 32 */
    int       volume;         /* 4 */
    uint64_t  magic_0;        /* 8 */
    uint64_t  magic_1;        /* 8 */
    int       type;           /* 4 */
    int       status;         /* 4 */
    int       format;         /* 4 */
    unsigned  generation;     /* 4 */
    uint64_t  total_sectors;  /* 8 */
    uint64_t  offset_sectors; /* 8 */
    uint16_t  heads;          /* 2 */
    uint16_t  sectors;        /* 2 */
    uint32_t  cylinders;      /* 4 */
    unsigned  width;          /* 4 */
    unsigned  interleave;     /* 4 */
    unsigned  total_disks;    /* 4 */
    struct ar_disk disks[MAX_DISKS];  /* the overflow target */
};

/* faithful lsiv2_raid_conf (ata-raid.h:459). We only need configs[30],
 * disk_number, raid_number. */
struct lsiv2_config {
    uint8_t  raw[16];   /* sized like the union raid/disk member (~16 B) */
};
struct lsiv2_raid_conf {
    uint8_t  lsi_id[6];
    uint8_t  dummy_0, flags;
    uint16_t version;
    uint8_t  config_entries, raid_count, total_disks, dummy_1;
    uint16_t dummy_2;
    struct lsiv2_config configs[30];
    uint8_t  disk_number;
    uint8_t  raid_number;
    uint32_t timestamp;
    uint8_t  filler[10];
};

int main(void)
{
    printf("=== DF-1172 struct layout (faithful, LP64) ===\n");
    printf("sizeof(struct ar_disk)          = %zu\n", sizeof(struct ar_disk));
    printf("sizeof(struct ar_softc_prefix)  = %zu\n", sizeof(struct ar_softc_prefix));
    printf("offsetof disks[0]               = %zu\n", offsetof(struct ar_softc_prefix, disks));
    printf("offsetof disks[%d] (first OOB)  = %zu\n", MAX_DISKS,
           offsetof(struct ar_softc_prefix, disks) + MAX_DISKS * sizeof(struct ar_disk));
    printf("MAX_ARRAYS=%d  MAX_DISKS=%d  configs[30]\n\n", MAX_ARRAYS, MAX_DISKS);

    /* craft malicious metadata */
    struct lsiv2_raid_conf meta;
    memset(&meta, 0, sizeof meta);
    meta.raid_number = 200;   /* BUG 1 & 2 trigger: >= 16 (BSS OOB) and >= 30 (configs OOB read) */
    meta.disk_number = 200;   /* BUG 3 trigger: >= 16 (heap OOB) */

    /* --- BUG 1: raidp[array + raid_number] OOB into ata_raid_arrays[16] --- */
    unsigned bss_oob_writes = 0;
    int worst_bss_idx = -1;
    /* the kernel loop: for(array=0; array<MAX_ARRAYS; array++) raidp[array+rn]... */
    for (int array = 0; array < MAX_ARRAYS; array++) {
        int idx = array + meta.raid_number;
        if (idx >= MAX_ARRAYS) {
            bss_oob_writes++;
            if (idx > worst_bss_idx) worst_bss_idx = idx;
        }
    }
    printf("BUG 1 (ata-raid.c:2802-2803): raidp[array+%u] over %d array iters\n",
           meta.raid_number, MAX_ARRAYS);
    printf("   -> %u BSS-OOB pointer stores into ata_raid_arrays[16..], worst idx=%d\n",
           bss_oob_writes, worst_bss_idx);
    printf("   -> %ld bytes past the 16-slot array (worst), clobbering adjacent BSS\n",
           (long)((worst_bss_idx - MAX_ARRAYS + 1) * (long)sizeof(void *)));

    /* --- BUG 2: configs[raid_entry] OOB read, raid_entry=raid_number --- */
    int raid_entry = meta.raid_number;
    printf("\nBUG 2 (ata-raid.c:2818-2822): configs[%d] (configs[30]) -> %s\n",
           raid_entry, raid_entry >= 30 ? "OOB READ (CWE-125)" : "in-bounds");

    /* --- BUG 3: raid->disks[disk_number] OOB heap write --- */
    int disk_number = meta.disk_number;
    unsigned heap_oob_entries = (disk_number >= MAX_DISKS) ? (256 - MAX_DISKS) : 0;
    /* entries actually written: only those for which the device != LSIV2_D_NONE
     * branch is taken. Worst case all of 16..255 -> (256-16) entries. */
    size_t disks_off = offsetof(struct ar_softc_prefix, disks);
    size_t struct_end = sizeof(struct ar_softc_prefix);   /* end of disks[] */
    size_t bytes_inside = 0, bytes_past = 0;
    if (disk_number >= MAX_DISKS) {
        /* bytes from disks[16] up to end of the (prefix) struct = inside spill */
        /* full ar_softc has trailing fields after disks[]; model them: */
        size_t trailing = 4/*toggle*/ + 8/*rebuild_lba*/ + 8/*lock*/ + 200/*disk*/
                        + 200/*devstat*/ + 8/*cdev*/ + 8/*pid*/; /* approx */
        size_t ar_total = struct_end + trailing;
        size_t spill_start = disks_off + MAX_DISKS * sizeof(struct ar_disk); /* == struct_end */
        size_t spill_end    = disks_off + disk_number * sizeof(struct ar_disk)
                            + sizeof(struct ar_disk);
        bytes_inside = (spill_end <= ar_total) ? (spill_end - spill_start)
                                               : (ar_total - spill_start);
        bytes_past   = (spill_end > ar_total) ? (spill_end - ar_total) : 0;
    }
    printf("\nBUG 3 (ata-raid.c:2866-2869): raid->disks[%d] (disks[%d])\n",
           disk_number, MAX_DISKS);
    printf("   -> %u OOB ar_disk entries (each %zu B) = %zu total OOB bytes\n",
           heap_oob_entries, sizeof(struct ar_disk),
           heap_oob_entries * sizeof(struct ar_disk));
    printf("   -> ~%zu bytes clobber trailing ar_softc fields, ~%zu bytes spill into adjacent heap\n",
           bytes_inside, bytes_past);

    int confirmed = (bss_oob_writes > 0) || (raid_entry >= 30) || (heap_oob_entries > 0);
    if (confirmed)
        printf("\nDF-1172: CONFIRMED 3-site OOB in ata_raid_lsiv2_read_meta "
               "(raid_number=%u, disk_number=%u)\n", meta.raid_number, meta.disk_number);
    else
        printf("\nDF-1172: NOT reproduced\n");

    /* ---- WITH FIX: reject metadata whose raid_number/disk_number are OOB ----
     * The applied fix.diff adds, right after the "$XIDE$" magic check:
     *   if (meta->raid_number >= MAX_ARRAYS || meta->disk_number >= MAX_DISKS)
     *       goto lsiv2_out;          // reject before the loop runs
     * so for crafted metadata the loop body never executes -> 0 OOB stores. */
    printf("\n--- WITH FIX (reject if raid_number>=MAX_ARRAYS || disk_number>=MAX_DISKS) ---\n");
    int reject = (meta.raid_number >= MAX_ARRAYS) || (meta.disk_number >= MAX_DISKS);
    unsigned fix_bss = 0, fix_cfg = 0, fix_disks = 0;
    int rn, dn;
    if (reject) {
        /* loop never runs */
        rn = -1; dn = -1;
    } else {
        rn = meta.raid_number; dn = meta.disk_number;
        for (int array = 0; array < MAX_ARRAYS; array++)
            if (array + rn >= MAX_ARRAYS) fix_bss++;
        if (rn >= 30) fix_cfg++;
        if (dn >= MAX_DISKS) fix_disks++;
    }
    printf("metadata %s -> loop %s\n", reject ? "REJECTED" : "accepted",
           reject ? "never runs" : "runs in-bounds");
    printf("BSS-OOB stores=%u ; configs OOB=%u ; disks OOB=%u (all expect 0)\n",
           fix_bss, fix_cfg, fix_disks);
    printf("DF-1172 FIX: %s\n", (fix_bss == 0 && fix_cfg == 0 && fix_disks == 0) ?
           "VALIDATED - range rejection eliminates all 3 OOB sites" : "INCOMPLETE");
    return confirmed ? 0 : 1;
}
