DragonFlyBSD Kernel Audit
DF-1171 / overflow_harness.c
← back to finding ↓ download raw
/*
 * DF-1171 harness — replicates the EXACT overflow math of
 * ata_raid_intel_read_meta() in sys/dev/disk/nata/ata-raid.c
 *
 * This is a userspace replica of the in-kernel data flow:
 *   raid->total_disks = map->total_disks;        // ata-raid.c:2230  (no clamp)
 *   for (disk=0; disk < raid->total_disks; disk++) // ata-raid.c:2243
 *       raid->disks[disk].* = ...;                  // writes past disks[15]
 *
 * It proves:
 *  1. sizeof(struct ar_disk) == 48 on LP64 (8+16+8+8+4+pad)
 *  2. sizeof(struct ar_softc) and the byte offset where disks[] overflows
 *     into the trailing lock/disk/devstat/cdev/pid fields, then past the struct
 *  3. With map->total_disks = 255 (u8 max), the loop writes
 *     (255-16)*48 = 11472 bytes past disks[15]
 *
 * Build:  cc -Wall -O2 -o overflow_harness overflow_harness.c
 * Run:    ./overflow_harness
 */

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

/* ---- faithful copies of the kernel struct layouts (ata-raid.h) ---- */

typedef unsigned char u_int8_t;
typedef unsigned short u_int16_t;
typedef unsigned int u_int;
typedef unsigned long u_int64_t;
typedef long off_t;

typedef void *device_t;
typedef void *cdev_t;

#define MAX_DISKS 16
#define MAX_VOLUMES 4

/* minimal stand-ins for the kernel objects that follow disks[] */
struct lock   { char _pad[64]; };
struct disk   { char _pad[256]; };
struct devstat{ char _pad[96]; };
struct proc   { char _pad[16]; };

struct ar_softc {
    int          lun;
    u_int8_t     name[32];
    int          volume;
    u_int64_t    magic_0;
    u_int64_t    magic_1;
    int          type;
    int          status;
    int          format;
    u_int        generation;
    u_int64_t    total_sectors;
    u_int64_t    offset_sectors;
    u_int16_t    heads;
    u_int16_t    sectors;
    u_int32_t    cylinders;
    u_int        width;
    u_int        interleave;
    u_int        total_disks;
    struct ar_disk {
        device_t   dev;
        u_int8_t   serial[16];
        u_int64_t  sectors;
        off_t      last_lba;
        u_int      flags;
    } disks[MAX_DISKS];
    int          toggle;
    u_int64_t    rebuild_lba;
    struct lock  lock;
    struct disk  disk;
    struct devstat devstat;
    cdev_t       cdev;
    struct proc *pid;
};

struct intel_raid_mapping {
    u_int8_t  name[16];
    u_int64_t total_sectors __attribute__((packed));
    u_int32_t state;
    u_int32_t reserved;
    u_int32_t filler_0[20];
    u_int32_t offset;
    u_int32_t disk_sectors;
    u_int32_t stripe_count;
    u_int16_t stripe_sectors;
    u_int8_t  status;
    u_int8_t  type;
    u_int8_t  total_disks;          /* <-- attacker-controlled u8, up to 255 */
    u_int8_t  magic[3];
    u_int32_t filler_1[7];
    u_int32_t disk_idx[1];
} __attribute__((packed));

int main(void) {
    printf("== DF-1171 overflow harness ==\n\n");
    printf("sizeof(struct ar_disk)            = %zu\n", sizeof(((struct ar_softc*)0)->disks[0]));
    printf("MAX_DISKS                         = %d\n", MAX_DISKS);
    printf("sizeof(struct ar_softc)           = %zu\n", sizeof(struct ar_softc));
    printf("sizeof(struct ar_softc).disks[]   = %zu\n", sizeof(((struct ar_softc*)0)->disks));
    printf("\n");

    struct ar_softc *rdp = (struct ar_softc *)calloc(1, sizeof(struct ar_softc) + 65536);
    /* alias the trailing fields so we can name what gets clobbered */
    unsigned char *base = (unsigned char *)rdp;
    unsigned char *disks_base = (unsigned char *)&rdp->disks[0];
    unsigned char *disks_end  = (unsigned char *)&rdp->disks[MAX_DISKS];
    unsigned char *struct_end = base + sizeof(struct ar_softc);

    printf("offset of disks[0]      = %zu\n", (size_t)(disks_base - base));
    printf("offset of disks[15]     = %zu (last valid)\n",
           (size_t)((unsigned char *)&rdp->disks[15] - base));
    printf("offset of disks[16]     = %zu (FIRST OOB write)\n",
           (size_t)(disks_end - base));
    printf("offset of end of struct = %zu\n", (size_t)(struct_end - base));
    printf("  -> toggle     @ %zu\n", (size_t)((unsigned char *)&rdp->toggle - base));
    printf("  -> rebuild_lba@ %zu\n", (size_t)((unsigned char *)&rdp->rebuild_lba - base));
    printf("  -> lock       @ %zu\n", (size_t)((unsigned char *)&rdp->lock - base));
    printf("  -> disk       @ %zu\n", (size_t)((unsigned char *)&rdp->disk - base));
    printf("  -> devstat    @ %zu\n", (size_t)((unsigned char *)&rdp->devstat - base));
    printf("  -> cdev       @ %zu\n", (size_t)((unsigned char *)&rdp->cdev - base));
    printf("  -> pid        @ %zu\n", (size_t)((unsigned char *)&rdp->pid - base));
    printf("\n");

    /* the bug: replicate the loop with attacker total_disks = 255 */
    struct intel_raid_mapping map;
    memset(&map, 0, sizeof(map));
    map.total_disks = 255;   /* u8 max */

    int overflow_writes = 0;
    for (int disk = 0; disk < map.total_disks; disk++) {
        if (disk >= MAX_DISKS) {
            overflow_writes++;
            /* poison the slot to visualize the OOB write */
            unsigned char *target = (unsigned char *)&rdp->disks[disk];
            memset(target, 0xAB, sizeof(rdp->disks[0]));
        }
    }

    size_t oob_bytes = (size_t)overflow_writes * sizeof(rdp->disks[0]);
    size_t past_struct = 0;
    if (disks_end + oob_bytes > struct_end)
        past_struct = (disks_end + oob_bytes) - struct_end;

    printf("== attacker sets map->total_disks = %d ==\n", map.total_disks);
    printf("loop writes raid->disks[0..%d]; valid range is [0..%d]\n",
           map.total_disks - 1, MAX_DISKS - 1);
    printf("OOB entries written past disks[15]: %d\n", overflow_writes);
    printf("OOB bytes written past disks[15]:   %zu  (=%d * %zu)\n",
           oob_bytes, overflow_writes, sizeof(rdp->disks[0]));
    printf("  of which INSIDE ar_softc struct : %zu bytes (clobbers toggle/rebuild_lba/lock/disk/devstat/cdev/pid)\n",
           oob_bytes - past_struct);
    printf("  of which PAST the struct boundary: %zu bytes (into adjacent kernel heap)\n",
           past_struct);
    printf("\n");

    /* name the first victim field clobbered */
    printf("first OOB write lands at struct offset %zu ", (size_t)(disks_end - base));
    if ((size_t)(disks_end - base) == (size_t)((unsigned char*)&rdp->toggle - base))
        printf("== ar_softc.toggle (then rebuild_lba, lock, disk, devstat, cdev, pid, then heap)\n");
    printf("\n");

    /* demonstrate attacker-controlled content (serial+sectors are meta-derived) */
    printf("Per OOB entry, attacker-controlled bytes:\n");
    printf("  serial[16] (16) + sectors (8) = 24 of %zu bytes attacker-shaped; "
           "dev/last_lba/flags also written (NULL/0/0 by the loop)\n", sizeof(rdp->disks[0]));

    free(rdp);
    printf("RESULT: OOB heap write CONFIRMED — %zu bytes past disks[15] for total_disks=%d\n",
           oob_bytes, map.total_disks);
    return 0;
}