DragonFlyBSD Kernel Audit
DF-0987 / cdcheckmedia_harness.c
← back to finding ↓ download raw
/*
 * DF-0987 — userspace harness replicating the TOC-parsing logic of
 * cdcheckmedia() in sys/bus/cam/scsi/scsi_cd.c.
 *
 * The kernel computes num_entries = (ending_track - starting_track) + 2 with
 * NO upper bound, then issues a READ_TOC DMA of toclen+4 bytes into
 * &softc->toc, where:
 *     struct cd_tocdata { struct ioc_toc_header header;      // 4 bytes
 *                         struct cd_toc_entry  entries[100]; }; // 800 bytes
 * so sizeof(cd_tocdata) == 804. A malicious SCSI CD returning
 * ending_track=255, starting_track=0 yields num_entries=257, toclen=2056, and
 * DMA writes 2060 bytes into the 804-byte buffer -> 1256-byte heap overflow,
 * plus entries[256] OOB write.
 *
 * The harness feeds that malicious header through the exact arithmetic and
 * shows the overflow. Compile again with -DFIXED for the patched behavior.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define NENTRIES       100
#define TOCENT_SZ      8        /* sizeof(struct cd_toc_entry) */
#define HDR_SZ         4        /* sizeof(struct ioc_toc_header) */
#define TOCDATA_SZ     (HDR_SZ + NENTRIES*TOCENT_SZ)   /* 804 */

struct ioc_toc_header { uint8_t len[2], starting_track, ending_track; };

int main(void)
{
    /* model the softc->toc region + adjacent softc memory */
    uint8_t region[TOCDATA_SZ + 1024];
    uint8_t *softc = region;
    memset(region, 0x00, sizeof(region));
    memset(region + TOCDATA_SZ, 0xCC, sizeof(region) - TOCDATA_SZ); /* canary */

    /* malicious READ_TOC header returned by the device */
    struct ioc_toc_header *toch = (struct ioc_toc_header *)softc;
    toch->starting_track = 0;
    toch->ending_track   = 255;

    int num_entries = (toch->ending_track - toch->starting_track) + 2;
    printf("starting_track=%u ending_track=%u -> num_entries=%d (cap=%d)\n",
           toch->starting_track, toch->ending_track, num_entries, NENTRIES);

#ifdef FIXED
    if (num_entries > NENTRIES) {
        printf("FIX: num_entries=%d > %d -> bailout (overflow prevented)\n",
               num_entries, NENTRIES);
        return 0;
    }
#endif

    if (num_entries <= 0) { printf("num_entries<=0 bailout\n"); return 0; }

    int toclen = num_entries * TOCENT_SZ;            /* 257*8 = 2056 */
    int dma_len = toclen + HDR_SZ;                    /* 2060 */

    printf("toclen=%d  DMA length into &softc->toc = %d  (buffer = %d)\n",
           toclen, dma_len, TOCDATA_SZ);

    /* simulate the device DMA fill (kernel trusts the length) */
    if (dma_len <= (int)sizeof(region)) {
        memset(softc, 0xAB, dma_len);                 /* corrupts canary tail */
    } else {
        printf("(DMA length exceeds harness region; clamped for demo)\n");
        memset(softc, 0xAB, sizeof(region));
    }

    /* the kernel then does entries[num_entries-1] = entries[256] OOB write */
    int oob_index = num_entries - 1;                  /* 256 */
    printf("subsequent entries[%d] write (cap=%d) -> OOB by %d entries\n",
           oob_index, NENTRIES, oob_index - (NENTRIES-1));

    long overflow_bytes = (long)dma_len - TOCDATA_SZ;
    int canary_hits = 0;
    for (int i = TOCDATA_SZ; i < (int)sizeof(region); i++)
        if (softc[i] != 0xCC) canary_hits++;

    printf("overflow past softc->toc: %ld bytes (clobbered %d tail bytes)\n",
           overflow_bytes, canary_hits);
    if (overflow_bytes > 0)
        printf("RESULT: OVERFLOW CONFIRMED — %ld-byte heap overflow into "
               "softc / M_DEVBUF\n", overflow_bytes);
    else
        printf("RESULT: NO OVERFLOW\n");
    return (overflow_bytes > 0) ? 1 : 0;
}