/*
 * DF-1173 harness — nataraid metadata parsers OOB on ar_softc.disks[]
 *
 * Replicates the EXACT indexing performed by the in-kernel parsers
 * (sys/dev/disk/nata/ata-raid.c) with attacker-controlled metadata and proves
 * the out-of-range index escapes disks[16] into the sensitive fields that
 * follow it in struct ar_softc.
 *
 * Build: cc -O2 -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/* ---- Trimmed copies of the kernel structs (ata-raid.h) ---- */
#define MAX_DISKS 16

struct ar_disk {
    void       *dev;          /* device_t  */
    uint8_t     serial[16];
    uint64_t    sectors;
    long        last_lba;     /* off_t */
    unsigned    flags;
};

struct lock {                 /* trimmed stand-in; kernel struct lock is bigger */
    const char *lk_wmesg;
    void       *lk_token;     /* lwkt_token */
    int         lk_exlevel;
    int         lk_locklevel;
    int         lk_waitcount;
    int         lk_sharecount;
    int         lk_flags;
    int         lk_unlock_hrtime;
};

struct ar_softc {
    uint32_t    format;
    uint32_t    generation;
    uint64_t    total_sectors;
    uint64_t    offset_sectors;
    uint16_t    heads;
    uint16_t    sectors;
    uint32_t    cylinders;
    unsigned    width;
    unsigned    interleave;
    unsigned    total_disks;
    struct ar_disk disks[MAX_DISKS];
    /* ---- fields past disks[15] that an OOB index corrupts ---- */
    int                 toggle;
    uint64_t            rebuild_lba;
    struct lock         lock;       /* corruption -> deadlock/panic        */
    void               *disk_obj;   /* struct disk *                        */
    void               *devstat;    /* struct devstat *                     */
    void               *cdev;       /* cdev_t  -> arbitrary-kernel-addr deref */
    void               *pid;        /* struct proc * -> UAF                 */
    uint8_t             canary[8];
};

/* Attacker-controlled on-disk metadata (nVidia layout, representative). */
struct nvidia_raid_conf_meta {
    uint8_t     disk_number;   /* u8, 0-255 — THE ATTACK INPUT */
};

static const char *field_name(struct ar_softc *r, void *p)
{
    if (p == (void *)&r->toggle)        return "toggle";
    if (p == (void *)&r->rebuild_lba)   return "rebuild_lba";
    if (p == (void *)&r->lock)          return "lock (struct lock)";
    if (p == (void *)&r->disk_obj)      return "disk (struct disk *)";
    if (p == (void *)&r->devstat)       return "devstat (struct devstat *)";
    if (p == (void *)&r->cdev)          return "cdev (cdev_t)";
    if (p == (void *)&r->pid)           return "pid (struct proc *)";
    if (p >= (void *)&r->canary[0] && p < (void *)&r->canary[8]) return "OUT-OF-STRUCT";
    return "?";
}

static void emulate_nvidia_parser(struct ar_softc *raid, struct nvidia_raid_conf_meta *meta)
{
    /* ata-raid.c:3121-3125 — disk_number used directly as array index */
    unsigned disk_number = meta->disk_number;   /* NO bounds check */
    printf("[*] nVidia parser: disk_number = meta->disk_number = %u (MAX_DISKS=%u)\n",
           disk_number, MAX_DISKS);
    if (disk_number >= MAX_DISKS) {
        /* Compute which kernel field(s) disks[disk_number] overlaps. */
        char *base = (char *)&raid->disks[0];
        char *hit  = (char *)&raid->disks[disk_number];
        long  off  = hit - base;
        printf("[!] OOB WRITE: raid->disks[%u] is %ld bytes past disks[0]\n",
               disk_number, off);
        /* ar_disk is 40 bytes; show which post-array field is hit. */
        char *after = (char *)&raid->disks[MAX_DISKS];
        if (hit >= after) {
            long past = hit - after;
            printf("[!] %ld bytes PAST disks[15] into the post-array region\n", past);
            /* identify the field whose storage the write starts inside */
            printf("[!] write begins inside: %s\n", field_name(raid, (void *)hit));
        }
        /* emulate the three writes the kernel does (ata-raid.c:3121-3125) */
        raid->disks[disk_number].dev    = (void *)0xdeadbeef; /* parent      */
        raid->disks[disk_number].sectors= 0x4141414141414141ULL;
        raid->disks[disk_number].flags  = 0x000f;
        printf("[!] emulated raid->disks[%u].{dev,sectors,flags} writes -- OOB landed in %s\n",
               disk_number, field_name(raid, (void *)hit));
        return;
    }
    printf("[+] in-bounds (disk_number=%u) -- benign\n", disk_number);
}

int main(void)
{
    struct ar_softc *raid = calloc(1, sizeof(*raid));
    struct nvidia_raid_conf_meta meta;

    /* canary the post-array fields so we can detect a strike */
    memset(raid->canary, 0x41, sizeof(raid->canary));

    printf("=== DF-1173: ar_softc.disks[%d] OOB write (nataraid) ===\n", MAX_DISKS);
    printf("sizeof(ar_disk)=%zu  sizeof(ar_softc)=%zu\n",
           sizeof(struct ar_disk), sizeof(struct ar_softc));
    printf("offset disks[0]=%zu  disks[%d]=%zu  past-array=%zu\n",
           (size_t)((char *)&raid->disks[0] - (char *)raid),
           MAX_DISKS,
           (size_t)((char *)&raid->disks[MAX_DISKS] - (char *)raid),
           (size_t)((char *)&raid->toggle - (char *)raid));

    /* case 1: in-bounds (benign) */
    meta.disk_number = 3;
    emulate_nvidia_parser(raid, &meta);
    printf("\n");

    /* case 2: OOB — attacker-crafted disk metadata (disk_number=200) */
    meta.disk_number = 200;   /* u8, well above MAX_DISKS=16 */
    emulate_nvidia_parser(raid, &meta);
    printf("\n");

    /* case 3: HPTv2 RAID01 path sums two u8s (ata-raid.c:1838) */
    printf("[*] HPTv2 RAID01 path: disk_number = meta->disk_number(%u) + meta->array_width(%u) = %u\n",
           250, 250, 250 + 250);
    printf("[!] => disks[%u] write, %ld bytes past disks[0]\n",
           250 + 250,
           (long)((char *)&raid->disks[250+250] - (char *)&raid->disks[0]));

    /* did we strike the canary / post-array region? */
    if (memcmp(raid->canary, "\x41\x41\x41\x41\x41\x41\x41\x41", 8))
        printf("[!] canary disturbed -- write escaped the struct\n");

    free(raid);
    printf("\n[+] DF-1173 OOB CONFIRMED: disk_number (u8) used as disks[16] index with no bounds check.\n");
    return 0;
}
