/*
 * DF-0586 deterministic kernel harness.
 *
 * The race the finding describes is between hci_mtap's LIST_FOREACH walk of
 * the global hci_pcb list (sys/netbt/hci_socket.c:935) and hci_sdetach's
 * unlocked LIST_REMOVE + kfree(pcb) (sys/netbt/hci_socket.c:576-577). On a
 * guest with NO Bluetooth controller, hci_mtap is unreachable from userspace:
 * hci_send returns ENETDOWN at sys/netbt/hci_socket.c:505 (no unit), and the
 * netisr input path (sys/netbt/bt_input.c) only iterates hci_unit_list which
 * is empty.
 *
 * This harness manufactures the missing conditions deterministically:
 *   - It allocates N fake hci_pcb objects (mirror of the private struct in
 *     hci_socket.c:72) and inserts them into the REAL global hci_pcb list.
 *   - A walker thread calls hci_mtap(fake_mbuf, fake_unit) in a tight loop,
 *     exercising the exact LIST_FOREACH at hci_socket.c:935.
 *   - A killer thread repeatedly does LIST_REMOVE + kfree(pcb, M_PCB) on one
 *     of the fake pcbs and immediately re-inserts a fresh one — the exact
 *     mutation hci_sdetach performs at hci_socket.c:576-577, except here
 *     driven from another CPU without the socket layer.
 *
 * The walker and killer run on different CPUs, so the unlocked walk-vs-free
 * race fires within seconds. On the INVARIANTS-ON default GENERIC kernel,
 * kfree() poisons the freed chunk with WEIRD_ADDR (0xdeadc0de) via
 * chunk_mark_free in kern_slaballoc.c, so the walker's next LIST_NEXT read
 * returns 0xdeadc0de and the next loop iteration page-faults dereferencing
 * it: "Fatal trap 12: page fault while in kernel mode".
 *
 * On the PATCHED kernel: hci_mtap holds hci_pcb_lock LK_SHARED while the
 * killer (mimicking hci_sdetach) — IF it took the lock — would acquire
 * LK_EXCLUSIVE. The fix.diff adds the lock to hci_sdetach and hci_mtap; to
 * prove the fix works on the patched kernel we instead drive the REAL
 * hci_sdetach via userspace socket() / close() (see poc_race.c), which takes
 * the patched lock, and confirm no panic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/kthread.h>
#include <netbt/bluetooth.h>
#include <netbt/hci.h>

/*
 * Mirror of the private hci_pcb struct from sys/netbt/hci_socket.c:72.
 * The layout MUST match for LIST_NEXT to read the right offset and for
 * hci_mtap's reads of hp_flags / hp_laddr / hp_pfilter / hp_efilter /
 * hp_socket to land on valid (zeroed) memory.
 */
struct hci_pcb {
	struct socket		*hp_socket;
	unsigned int		 hp_flags;
	bdaddr_t		 hp_laddr;
	bdaddr_t		 hp_raddr;
	struct hci_filter	 hp_efilter;
	struct hci_filter	 hp_pfilter;
	LIST_ENTRY(hci_pcb)	 hp_next;
};
LIST_HEAD(hci_pcb_list_head, hci_pcb);

/* The global hci_pcb list (BSS symbol exported by netbt.ko). Its head type
 * is LIST_HEAD(hci_pcb_list, hci_pcb), which expands to a struct with one
 * member (lh_first). Our local head type matches in layout. */
extern struct hci_pcb_list_head hci_pcb;

/* M_PCB used by hci_socket.c is `M_BLUETOOTH`. We reuse it so kfree() returns
 * the chunk to the same slab bucket as a real hci_pcb free. */
#define	PCB_MALLOC_TYPE		M_BLUETOOTH
#define	PCB_MALLOC_FLAGS	(M_WAITOK | M_ZERO)

#define	NFAKE		32

static struct hci_unit fake_unit;		/* BSS, hci_bdaddr = BDADDR_ANY */
static struct mbuf *fake_m;
static struct hci_pcb *fakes[NFAKE];
static struct thread *walker_td, *killer_td;
static int harness_stop = 0;
static unsigned long wcycles = 0, kcycles = 0;
static int harness_mode = 1;	/* 0 = self-contained killer+walker
				 * 1 = walker-only (default; for both
				 *     unpatched AND patched test where
				 *     userspace drives the real
				 *     hci_sdetach via socket()/close()) */
static int walker_cpu = 1, killer_cpu = 2;

SYSCTL_NODE(_kern, OID_AUTO, df0586, CTLFLAG_RD, 0, "DF-0586 harness");
SYSCTL_ULONG(_kern_df0586, OID_AUTO, wcycles, CTLFLAG_RD, &wcycles, 0,
	     "walker hci_mtap() calls");
SYSCTL_ULONG(_kern_df0586, OID_AUTO, kcycles, CTLFLAG_RD, &kcycles, 0,
	     "killer remove+free+reinsert cycles");
SYSCTL_INT(_kern_df0586, OID_AUTO, mode, CTLFLAG_RD, &harness_mode, 0,
	   "0=self-contained, 1=walker-only");
SYSCTL_INT(_kern_df0586, OID_AUTO, walker_cpu, CTLFLAG_RD, &walker_cpu, 0, "");
SYSCTL_INT(_kern_df0586, OID_AUTO, killer_cpu, CTLFLAG_RD, &killer_cpu, 0, "");

static void
insert_fake(int i)
{
	fakes[i] = kmalloc(sizeof(struct hci_pcb), PCB_MALLOC_TYPE, PCB_MALLOC_FLAGS);
	/* Make hci_mtap's first filter FAIL on our fake pcbs so it `continue`s
	 * past them without dereferencing hp_socket (which is NULL). Set
	 * hp_laddr to all-FF, while fake_unit.hci_bdaddr is all-zero, so
	 * bdaddr_same returns false and the `continue` at line 940-941 fires. */
	memset(&fakes[i]->hp_laddr, 0xff, sizeof(bdaddr_t));
	/* Still unlocked insert, just like hci_sattach does at hci_socket.c:656 */
	LIST_INSERT_HEAD(&hci_pcb, fakes[i], hp_next);
}

/*
 * Walker: hci_mtap() does LIST_FOREACH(&hci_pcb) and reads pcb fields.
 * If a pcb is concurrently freed+poisoned by the killer, the walker's
 * next LIST_NEXT read returns 0xdeadc0de and the subsequent pcb != NULL /
 * pcb->hp_next dereference faults.
 */
static void
walker_loop(void *arg)
{
	int tick = 0;
	while (!harness_stop) {
		hci_mtap(fake_m, &fake_unit);
		wcycles++;
		if (++tick >= 256) {
			tick = 0;
			lwkt_yield();
		}
	}
}

/*
 * Killer: repeatedly remove + kfree one of our fake pcbs (exact same
 * mutation hci_sdetach performs at hci_socket.c:576-577), then re-insert a
 * fresh one. The walker is iterating the list concurrently on another CPU.
 */
static void
killer_loop(void *arg)
{
	int i = 0, tick = 0;
	while (!harness_stop) {
		i = (i + 1) % NFAKE;
		/* If our pcb was already removed (e.g., by killer interleave),
		 * skip; otherwise this matches hci_sdetach:576-577 exactly. */
		if (fakes[i] != NULL) {
			struct hci_pcb *p = fakes[i];
			LIST_REMOVE(p, hp_next);
			kfree(p, PCB_MALLOC_TYPE);
			fakes[i] = NULL;
			insert_fake(i);
			kcycles++;
		}
		if (++tick >= 64) {
			tick = 0;
			lwkt_yield();
		}
	}
}

static int
df0586_modevent(module_t mod, int type, void *data)
{
	int error = 0, i;
	switch (type) {
	case MOD_LOAD:
		/* Build a 4-byte mbuf: HCI_EVENT_PKT header (type byte +
		 * event byte +plen byte). hci_mtap reads mtod(m,uint8_t*)[0]
		 * for type then for HCI_EVENT_PKT reads event = byte[1]. */
		fake_m = m_gethdr(M_WAITOK, MT_DATA);
		if (fake_m == NULL) {
			kprintf("df0586: m_gethdr failed\n");
			return ENOMEM;
		}
		memset(mtod(fake_m, caddr_t), 0, 4);
		mtod(fake_m, uint8_t *)[0] = HCI_EVENT_PKT;
		/* event = 0 -> hci_filter_test(0, &efilter) reads mask[7]&0x80000000
		 * which is 0 for our zeroed fake pcbs -> filter fails -> continue.
		 * For real pcbs (default efilter), event=0 also filters out. */
		fake_m->m_len = 4;
		fake_m->m_pkthdr.len = 4;

		/* Pre-populate the global list with NFAKE entries */
		for (i = 0; i < NFAKE; i++)
			insert_fake(i);

		harness_stop = 0;
		wcycles = kcycles = 0;

		/* Walker always runs */
		error = kthread_create_cpu(walker_loop, NULL, &walker_td,
					   walker_cpu, "df0586w");
		if (error) goto fail;

		if (harness_mode == 0) {
			error = kthread_create_cpu(killer_loop, NULL,
						   &killer_td, killer_cpu,
						   "df0586k");
			if (error) goto fail;
			kprintf("df0586_harness: self-contained mode: "
				"walker cpu%d + killer cpu%d racing on "
				"%d fake pcbs in the global hci_pcb list\n",
				walker_cpu, killer_cpu, NFAKE);
		} else {
			kprintf("df0586_harness: walker-only mode (cpu%d); "
				"NOW run userspace poc_race to drive the real "
				"hci_sattach/hci_sdetach\n", walker_cpu);
		}
		break;
fail:
		harness_stop = 1;
		tsleep(&harness_stop, 0, "df0586e", 100);
		for (i = 0; i < NFAKE; i++) {
			if (fakes[i]) {
				LIST_REMOVE(fakes[i], hp_next);
				kfree(fakes[i], PCB_MALLOC_TYPE);
				fakes[i] = NULL;
			}
		}
		if (fake_m) { m_freem(fake_m); fake_m = NULL; }
		return error;
	case MOD_UNLOAD:
		harness_stop = 1;
		tsleep(&harness_stop, 0, "df0586u", 200);
		for (i = 0; i < NFAKE; i++) {
			if (fakes[i]) {
				LIST_REMOVE(fakes[i], hp_next);
				kfree(fakes[i], PCB_MALLOC_TYPE);
				fakes[i] = NULL;
			}
		}
		if (fake_m) { m_freem(fake_m); fake_m = NULL; }
		kprintf("df0586_harness unloaded: wcycles=%lu kcycles=%lu\n",
			wcycles, kcycles);
		break;
	default:
		break;
	}
	return error;
}

static moduledata_t df0586_mod = {
	"df0586_harness",
	df0586_modevent,
	NULL
};
DECLARE_MODULE(df0586_harness, df0586_mod, SI_SUB_EXEC, SI_ORDER_ANY);
MODULE_DEPEND(df0586_harness, netbt, 1, 1, 1);
MODULE_VERSION(df0586_harness, 1);
