/*
 * harness2.c -- KLD harness for the pass-2 audit of sys/netbt/hci_misc.c
 * (finding DF-5780: unsynchronized hci_memos list -> concurrent
 * hci_memo_find walkers double-free / UAF hci_memo records).
 *
 * The FULL production netbt stack (all sys/netbt sources, copied by
 * build.sh, md5s recorded) is compiled into this module: bt_proto.c
 * registers the real PF_BLUETOOTH domain and the real NETISR_BLUETOOTH
 * handler, so real
 *
 *	socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); connect(...)
 *
 * from an ordinary unprivileged userland process reaches the production
 * l2cap_connect() -> hci_route_lookup() -> hci_memo_find() walk in the
 * caller's process context (any CPU).
 *
 * This harness plays only the controller-driver role (what ubt(4)/
 * btuart(4) play in production): it attaches one unit through the
 * production hci_attach() and feeds HCI event packets through the
 * production input entry point
 *
 *	hci_input_event() -> netisr(NETISR_BLUETOOTH) -> btintr()
 *	  -> hci_intr() -> hci_event() -> hci_event_inquiry_result()
 *	  -> hci_memo_new() -> hci_memo_find()      [netisr-0 context]
 *
 * Triggers (sysctl, as root):
 *
 *   hw.hcir_setup = 1        attach fake unit, mark it UP/RUNNING
 *                            (the state after admin "btconfig hci0 up";
 *                             BTF_UP is what hci_route_lookup() tests)
 *   hw.hcir_seed = N         inject N Inquiry Result events (16 unique
 *                            bdaddrs each) -> N*16 hci_memo records
 *                            via the production event path
 *   hw.hcir_flood = 1/0      start/stop a kthread that continuously
 *                            injects inquiry events (the netisr-side
 *                            hci_memo_find walker + fresh-memo churn)
 *   hw.hcir_memos = 1        print current memo count on the unit
 *   hw.hcir_check_dup = N    allocate N live 48-byte blocks (same size
 *                            class/zone as struct hci_memo), check for
 *                            duplicate addresses among LIVE allocations
 *                            (proof of double-free / freelist corruption)
 *   hw.hcir_debug = N        set netbt bluetooth_debug (DPRINTF between
 *                            the expiry check and hci_memo_free widens
 *                            the race window when != 0)
 */

#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 <sys/kthread.h>
#include <sys/thread.h>

#include <net/netisr.h>
#include <sys/bus_private.h>	/* struct bsd_device */

#include <netbt/bluetooth.h>
#include <netbt/hci.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_enable(device_t dev)
{
	return 0;
}

static void
fake_disable(device_t dev)
{
}

static void
fake_output_cmd(device_t dev, struct mbuf *m)
{
	/* controller would answer CREATE_CON etc; we just drop */
	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 device_t fake_dev;

/*
 * Fabricate a minimal struct bsd_device so that the production
 * DPRINTF paths (device_get_nameunit / device_printf, enabled when
 * bluetooth_debug != 0) dereference valid memory.  Only nameunit and
 * desc are ever read by the netbt debug prints.
 */
static device_t
make_fake_device(void)
{
	struct bsd_device *dev;
	static char nameunit[] = "hci0";
	static char desc[] = "hcir fake controller";

	dev = kmalloc(sizeof(*dev), M_TEMP, M_ZERO | M_WAITOK);
	KKASSERT(dev != NULL);
	dev->nameunit = nameunit;
	dev->desc = desc;

	return (device_t)dev;
}

static int flood_run;
static struct thread *flood_td;
static volatile uint16_t bdctr;

/*
 * Build an mbuf carrying a complete HCI event packet (exactly what a
 * controller RX URB delivers) and hand it to the production input path.
 */
static void
inject_event(const uint8_t *pkt, int len)
{
	struct mbuf *m;

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

	hci_input_event(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))
			return 1;
		tsleep(g_unit, 0, "hcirw", hz / 10);
	}
	return (g_unit->hci_eventqlen == 0);
}

static int
memo_count(void)
{
	struct hci_memo *memo;
	int n = 0;

	LIST_FOREACH(memo, &g_unit->hci_memos, next)
		n++;
	return n;
}

/* build one Inquiry Result event with nresp responses (unique bdaddrs) */
static int
build_inquiry_pkt(uint8_t *pkt, int nresp)
{
	int off = 0, resp, i;

	pkt[off++] = 0x04;
	pkt[off++] = 0x02;			/* HCI_EVENT_INQUIRY_RESULT */
	pkt[off++] = 1 + nresp * 14;
	pkt[off++] = nresp;

	for (resp = 0; resp < nresp; resp++) {
		pkt[off++] = bdctr & 0xff;
		pkt[off++] = (bdctr >> 8) & 0xff;
		bdctr++;
		for (i = 0; i < 4; i++)
			pkt[off++] = 0xc0 + i;
		pkt[off++] = 0x01;		/* page_scan_rep_mode */
		pkt[off++] = 0x00;		/* page_scan_period */
		pkt[off++] = 0x00;		/* page_scan_mode */
		pkt[off++] = 0x22;		/* uclass[3] */
		pkt[off++] = 0x22;
		pkt[off++] = 0x22;
		pkt[off++] = 0x11;		/* clock_offset lo/hi */
		pkt[off++] = 0x11;
	}
	return off;
}

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

static int
hcir_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("[hcir] unit already attached\n");
		return 0;
	}

	kprintf("[hcir] === setup: attach unit via production hci_attach() ===\n");

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

	/*
	 * State after "btconfig hci0 up": unit initialised, enabled and
	 * BTF_UP set (this is what hci_route_lookup()/hci_unit_lookup()
	 * test, set by SIOCSBTFLAGS in production: hci_ioctl.c:242).
	 */
	g_unit->hci_flags |= BTF_UP | BTF_RUNNING;
	g_unit->hci_num_cmd_pkts = 1;

	/* environment: allow larger event bursts (production tunable,
	 * sysctl-less in this tree: sys/netbt/hci_unit.c:56) */
	hci_eventq_max = 512;

	kprintf("[hcir] unit %p attached, flags=%#x\n",
	    g_unit, g_unit->hci_flags);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_setup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_setup, "I", "attach fake HCI unit (UP+RUNNING)");

/* ------------------------------------------------------------------ */
/* seed expired memos via the production event path                    */

static int
hcir_seed(SYSCTL_HANDLER_ARGS)
{
	uint8_t pkt[3 + 1 + 16 * 14];
	int v = 0, err, ev, before, after, off;

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

	KKASSERT(g_unit != NULL);

	before = memo_count();
	kprintf("[hcir] === seed: %d Inquiry Result events (16 responses "
	    "each) -> hci_memo_new() ===\n", v);
	kprintf("[hcir] memos before: %d\n", before);

	for (ev = 0; ev < v; ev++) {
		off = build_inquiry_pkt(pkt, 16);
		inject_event(pkt, off);
	}

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

	after = memo_count();
	kprintf("[hcir] memos after: %d\n", after);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_seed, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_seed, "I", "seed memos via production inquiry events");

/* ------------------------------------------------------------------ */
/* continuous netisr-side walker (inquiry event flood)                 */

static void
flood_thread(void *arg)
{
	uint8_t pkt[3 + 1 + 16 * 14];
	int off, i, tick;

	kprintf("[hcir] flood thread started (netisr-side hci_memo_find "
	    "walker + memo churn; 4 events/tick, 3/4 repeated bdaddrs -> "
	    "hci_memo_new takes the found/update path)\n");
	tick = 0;
	while (flood_run) {
		for (i = 0; i < 4; i++) {
			if ((tick++ & 3) == 3)
				bdctr += 16;	/* fresh bdaddrs: new memos */
			/* else: repeat the previous 16 -> find MATCH ->
			 * hci_memo_new returns existing memo and writes
			 * memo->time (the UAF-write candidate path) */
			off = build_inquiry_pkt(pkt, 16);
			inject_event(pkt, off);
		}
		tsleep(g_unit, 0, "hcirf", 1);
	}
	kprintf("[hcir] flood thread stopped\n");
	wakeup(&flood_td);
	kthread_exit();
}

/*
 * Burst: inject N events back-to-back so netisr-0 runs N x 16
 * hci_memo_new -> hci_memo_find walks over the (all-expired) list
 * while user walkers are demolishing it too.
 */
static int
hcir_burst(SYSCTL_HANDLER_ARGS)
{
	uint8_t pkt[3 + 1 + 16 * 14];
	int v = 0, err, i, off;

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

	KKASSERT(g_unit != NULL);

	kprintf("[hcir] burst: %d events (%d walks) into netisr\n",
	    v, v * 16);
	for (i = 0; i < v; i++) {
		off = build_inquiry_pkt(pkt, 16);
		inject_event(pkt, off);
	}
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_burst, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_burst, "I", "burst N inquiry events into netisr");

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

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

	KKASSERT(g_unit != NULL);

	if (v && !flood_run) {
		flood_run = 1;
		err = kthread_create(flood_thread, NULL, &flood_td, "hcir_flood");
		if (err)
			flood_run = 0;
	} else if (!v && flood_run) {
		flood_run = 0;
		wakeup(g_unit);
		tsleep(&flood_td, 0, "hcirj", 5 * hz);
	}
	return err;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_flood, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_flood, "I", "continuous inquiry event flood (1=start 0=stop)");

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

static int
hcir_memos(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;

	kprintf("[hcir] memos on unit: %d\n", memo_count());
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_memos, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_memos, "I", "print memo count");

/* ------------------------------------------------------------------ */
/* duplicate-allocation detector: proof of double-free / freelist      */
/* corruption in the 48-byte size class (struct hci_memo's class).     */
/* kmalloc zones are GLOBAL PER SIZE CLASS in DragonFly (zoneindex(),  */
/* kern_slaballoc.c:638 -- no type parameter), so any two live         */
/* allocations handed the SAME address are a kernel-wide aliasing      */
/* primitive.                                                          */

static int
cmp_ptrs(const void *a, const void *b)
{
	void * const *pa = a, * const *pb = b;

	if (*pa < *pb)
		return -1;
	if (*pa > *pb)
		return 1;
	return 0;
}

static int
hcir_check_dup(SYSCTL_HANDLER_ARGS)
{
	void **a;
	uint8_t *p;
	int v = 0, err, i, n, dups = 0;

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

	n = v;
	a = kmalloc(n * sizeof(void *), M_TEMP, M_WAITOK);
	KKASSERT(a != NULL);

	for (i = 0; i < n; i++) {
		/* sizeof(struct hci_memo) == 48: same global size class */
		p = kmalloc(48, M_BLUETOOTH, M_NOWAIT | M_ZERO);
		if (p == NULL) {
			kprintf("[hcir] kmalloc failed at %d\n", i);
			n = i;
			break;
		}
		*(uint64_t *)p = 0xCAFEBABE00000000UL + i;
		a[i] = p;
	}

	kqsort(a, n, sizeof(void *), cmp_ptrs);

	for (i = 1; i < n; i++) {
		if (a[i] == a[i - 1]) {
			kprintf("[hcir] *** DUPLICATE LIVE ALLOCATION: %p "
			    "(blocks %d and %d) -- DOUBLE-FREE CONFIRMED, "
			    "two owners of one 48-byte kernel block\n",
			    a[i], i - 1, i);
			dups++;
		}
	}
	if (dups == 0)
		kprintf("[hcir] check_dup: %d live 48B blocks, no duplicate "
		    "addresses (freelist clean at this instant)\n", n);

	for (i = 0; i < n; i++) {
		p = a[i];
		if (dups && *(uint64_t *)p >> 32 != 0xCAFEBABEL)
			kprintf("[hcir] block %d (%p) canary clobbered: "
			    "%016jx\n", i, p, (uintmax_t)*(uint64_t *)p);
		kfree(p, M_BLUETOOTH);
	}
	kfree(a, M_TEMP);

	kprintf("[hcir] check_dup done: %d duplicate(s)\n", dups);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_check_dup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_check_dup, "I", "N live 48B kmallocs -> detect dup addrs");

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

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

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

	bluetooth_debug = v;
	kprintf("[hcir] bluetooth_debug = %d\n", bluetooth_debug);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_debug, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_debug, "I", "set netbt bluetooth_debug");

/* ------------------------------------------------------------------ */
/* live corruption watchdog: while the race is running, continuously   */
/* (a) time a production hci_memo_find() walk (circular/self-linked     */
/*     list from a double-inserted block => WALK STUCK), and           */
/* (b) allocate batches of canary'd 48-byte blocks and check for       */
/*     duplicate live addresses or clobbered canaries (double-free     */
/*     freelist dup, or aliasing with a concurrently-written           */
/*     hci_memo that overwrites offset 0 via microtime).               */

static int dup_hits, canary_hits, stuck_hits;

static int
dup_batch_check(int n)
{
	void **a;
	uint8_t *p;
	int i, dups = 0;

	a = kmalloc(n * sizeof(void *), M_TEMP, M_WAITOK);
	KKASSERT(a != NULL);

	for (i = 0; i < n; i++) {
		p = kmalloc(48, M_BLUETOOTH, M_NOWAIT | M_ZERO);
		if (p == NULL) {
			n = i;
			break;
		}
		*(uint64_t *)p = 0xCAFEBABE00000000UL + i;
		a[i] = p;
	}

	kqsort(a, n, sizeof(void *), cmp_ptrs);
	for (i = 1; i < n; i++) {
		if (a[i] == a[i - 1]) {
			kprintf("[hcir] *** LIVE DUP: %p (blocks %d,%d): "
			    "DOUBLE-FREE PRESENT IN 48B ZONE FREELIST\n",
			    a[i], i - 1, i);
			dups++;
		}
	}
	for (i = 0; i < n; i++) {
		p = a[i];
		if (*(uint64_t *)p >> 32 != 0xCAFEBABEL) {
			kprintf("[hcir] *** CANARY CLOBBERED: block %d (%p) "
			    "now %016jx -- block handed out twice and "
			    "written by another owner\n",
			    i, p, (uintmax_t)*(uint64_t *)p);
			dups++;
		}
	}
	for (i = 0; i < n; i++)
		kfree(a[i], M_BLUETOOTH);
	kfree(a, M_TEMP);
	return dups;
}

static int wd_run;
static struct thread *wd_td;

static void
watchdog_thread(void *arg)
{
	struct timeval t0, t1;
	bdaddr_t probe;
	int64_t usec;

	kprintf("[hcir] watchdog started\n");
	memset(&probe, 0xab, sizeof(probe));
	while (wd_run) {
		/* (a) walk probe */
		getmicrotime(&t0);
		hci_memo_find(g_unit, &probe);
		getmicrotime(&t1);
		usec = (t1.tv_sec - t0.tv_sec) * 1000000 +
		    (t1.tv_usec - t0.tv_usec);
		if (usec > 2000000) {
			kprintf("[hcir] *** WALK STUCK: hci_memo_find took "
			    "%jd us (circular memo list?)\n",
			    (intmax_t)usec);
			stuck_hits++;
		}

		/* (b) freelist dup / aliasing probe */
		if (dup_batch_check(512))
			dup_hits++;

		tsleep(g_unit, 0, "hcirwd", 2);
	}
	kprintf("[hcir] watchdog stopped (dup=%d canary-in-dup=%d "
	    "stuck=%d)\n", dup_hits, canary_hits, stuck_hits);
	wakeup(&wd_td);
	kthread_exit();
}

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

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

	KKASSERT(g_unit != NULL);

	if (v && !wd_run) {
		dup_hits = canary_hits = stuck_hits = 0;
		wd_run = 1;
		err = kthread_create(watchdog_thread, NULL, &wd_td,
		    "hcir_wdog");
		if (err)
			wd_run = 0;
	} else if (!v && wd_run) {
		wd_run = 0;
		wakeup(g_unit);
		tsleep(&wd_td, 0, "hcirj2", 2 * hz);
		kprintf("[hcir] watchdog totals: dup=%d stuck=%d\n",
		    dup_hits, stuck_hits);
	}
	return err;
}

SYSCTL_PROC(_hw, OID_AUTO, hcir_watchdog, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcir_watchdog, "I", "live corruption watchdog (1=start 0=stop)");

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

static int
harness_mode(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		/* bt_proto.c SYSINITs (compiled in verbatim) registered the
		 * PF_BLUETOOTH domain, netbt sysctls and NETISR_BLUETOOTH */
		kprintf("[hcir] hci_race_harness loaded (full production "
		    "netbt stack): hcir_setup, hcir_seed, hcir_flood, "
		    "hcir_memos, hcir_check_dup, hcir_debug\n");
		break;
	case MOD_UNLOAD:
		return EBUSY;
	default:
		break;
	}
	return 0;
}

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

DECLARE_MODULE(hci_race_harness, harness_moddata, SI_SUB_DRIVERS,
    SI_ORDER_MIDDLE);
