/*
 * harness5747.c -- KLD harness for DF-5747 (pass 2, sys/netbt/rfcomm_upper.c):
 *
 *   rfcomm_bind() (sys/netbt/rfcomm_upper.c:103-109) performs NO DLC state
 *   check -- it happily overwrites rd_laddr of a DLC that is already
 *   LISTENING (or mid-connect).  NetBSD upstream added
 *
 *	if (dlc->rd_state != RFCOMM_DLC_CLOSED)
 *		return EINVAL;
 *
 *   to rfcomm_bind_pcb() (NetBSD rfcomm_upper.c rev 1.12+); DragonFly never
 *   took that fix.  Impact: an unprivileged local user can re-bind a live
 *   listener, mutating its advertised channel/PSM underneath the listening
 *   session (state confusion -- the listener allocation scan in
 *   rfcomm_listen() and the best-match scan in rfcomm_dlc_newconn() both
 *   key off rd_laddr.bt_channel).
 *
 * 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, md5s recorded).  L2CAP is stubbed (l2cap_stub.c).
 *
 * Fire with:   sysctl hw.df5747_fire=1     (as root, inside the guest)
 * Expected:    "[DF-5747] bind() while LISTEN returned 0"  (BUG: upstream
 *              returns EINVAL); listener channel mutated 3 -> 5; clean
 *              teardown afterwards proves no crash, only wrong semantics.
 */

#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>

/* upper-layer (socket) callbacks -- log only, 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,
};

static int pool_ready;

static int
fire5747(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;
	struct rfcomm_dlc *dlc = NULL;
	struct sockaddr_bt laddr;

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

	kprintf("=== DF-5747 harness: rfcomm_bind() missing DLC state check ===\n");

	if (!pool_ready) {
		rfcomm_init();		/* credit pool (bt_proto.c normally) */
		pool_ready = 1;
	}

	/* local app: socket(), bind(channel 3), listen() */
	err = rfcomm_attach(&dlc, &upper_proto, NULL);
	KKASSERT(err == 0);
	rfcomm_rcvd(dlc, 4096);

	memset(&laddr, 0, sizeof(laddr));
	laddr.bt_len = sizeof(laddr);
	laddr.bt_family = AF_BLUETOOTH;
	laddr.bt_psm = L2CAP_PSM_RFCOMM;
	laddr.bt_channel = 3;

	err = rfcomm_bind(dlc, &laddr);
	kprintf("[DF-5747] bind #1 (state CLOSED)          -> %d (expect 0)\n", err);
	KKASSERT(err == 0);

	err = rfcomm_listen(dlc);
	kprintf("[DF-5747] listen()                        -> %d; state=%d (8=LISTEN), channel=%d\n",
	    err, dlc->rd_state, dlc->rd_laddr.bt_channel);
	KKASSERT(err == 0);
	KKASSERT(dlc->rd_state == RFCOMM_DLC_LISTEN);

	/*
	 * THE BUG: re-bind the LIVE listener (upstream: EINVAL).
	 */
	laddr.bt_channel = 5;			/* arbitrary new channel */
	laddr.bt_psm = 0x1001;			/* arbitrary odd PSM */
	err = rfcomm_bind(dlc, &laddr);
	kprintf("[DF-5747] bind #2 (state LISTEN)          -> %d  %s\n", err,
	    (err == 0 ? "(BUG: live listener mutated; upstream returns EINVAL)"
		     : "(EINVAL - fixed)"));
	kprintf("[DF-5747] listener now advertises: channel=%d psm=0x%04x state=%d\n",
	    dlc->rd_laddr.bt_channel, dlc->rd_laddr.bt_psm, dlc->rd_state);

	/*
	 * prove the mutated listener still tears down cleanly (no crash,
	 * pure semantic bug) -- production close path, exactly as
	 * rfcomm_sdetach() would drive it.
	 */
	err = rfcomm_disconnect(dlc, 0);
	kprintf("[DF-5747] disconnect() on LISTEN dlc      -> %d (expect EINVAL)\n",
	    err);
	rfcomm_detach(&dlc);
	kprintf("[DF-5747] detach() complete, dlc freed, listen session freed\n");
	kprintf("=== DF-5747 harness done ===\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, df5747_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fire5747, "I", "fire DF-5747 bind-no-state-check scenario");

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

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

DECLARE_MODULE(rfcomm_up5747, harness_moddata, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
