⬢ DragonFlyBSD Kernel Audit
DF-5748 / harness5748.c
← back to finding ↓ download raw
/*
 * harness5748.c -- KLD harness for DF-5748 (pass 2, sys/netbt/rfcomm_upper.c):
 *
 *   rfcomm_detach() (sys/netbt/rfcomm_upper.c:289-296) ends with
 *
 *	if (callout_active(&dlc->rd_timeout))
 *		dlc->rd_flags |= RFCOMM_DLC_DETACH;   // "let the callout release it"
 *	else
 *		kfree(dlc, M_BLUETOOTH);
 *
 *   and rfcomm_dlc_timeout() (sys/netbt/rfcomm_dlc.c:201-204) has the
 *   matching release branch:
 *
 *	if (dlc->rd_state != RFCOMM_DLC_CLOSED)
 *		rfcomm_dlc_close(dlc, ETIMEDOUT);
 *	else if (dlc->rd_flags & RFCOMM_DLC_DETACH)
 *		kfree(dlc, M_BLUETOOTH);
 *
 *   Under DragonFly's current callout implementation this entire mechanism
 *   is DEAD CODE:
 *     - rfcomm_dlc_close() always calls callout_stop() before detach's
 *       test (rfcomm_upper.c:278-279 -> rfcomm_dlc.c:164), and
 *     - _callout_cancel_or_stop() (sys/kern/kern_timeout.c:869) clears
 *       CALLOUT_ACTIVE unconditionally at entry, and (kern_timeout.c:888-921)
 *       DRAINS an in-progress callback before returning.
 *   => callout_active() at rfcomm_upper.c:293 is provably always 0,
 *   => RFCOMM_DLC_DETACH can never be set,
 *   => the kfree() branch in rfcomm_dlc_timeout() is unreachable.
 *
 *   Consequence: none exploitable by itself (the immediate kfree is safe
 *   precisely because callout_stop() drains), but the guard *looks* like it
 *   handles the callout-vs-detach race and does not -- the genuine race is
 *   the unsynchronized double-entry into rfcomm_dlc_close() already filed
 *   as DF-0724.  This dead "safety net" invites exactly the wrong fix.
 *
 * Demonstration (all production calls on verbatim code):
 *   1. drive two DLCs to OPEN through the real session state machine
 *      (injected TS 07.10 frames with valid FCS, exactly what L2CAP
 *      delivers; stub L2CAP layer),
 *   2. rfcomm_disconnect() both -> ack timer ARMED on each DLC
 *      (callout_pending == callout_active == 1),
 *   3. call rfcomm_dlc_close() on DLC A -- the identical call
 *      rfcomm_detach() makes at :279 -- and show callout_active() is
 *      already 0 afterwards, i.e. the :293 test can only see 0,
 *   4. run full production rfcomm_detach() on DLC B (clean teardown),
 *   5. let the session expiry callout fire (sleep) -- no crash.
 *
 * Fire with:   sysctl hw.df5748_fire=1     (as root, inside the guest)
 * Expected:    "[DF-5748] DEAD: callout_active() == 0 after
 *               rfcomm_dlc_close(); the RFCOMM_DLC_DETACH branch at
 *               rfcomm_upper.c:293 is unreachable"
 */

#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/callout.h>

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

extern struct l2cap_channel *harness_last_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.
 */
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)]

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

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

static void
inject_pn_resp(struct l2cap_channel *chan, int dlci, uint16_t mtu)
{
	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] = (uint8_t)dlci;
	info[3] = 0xe0;			/* flow_control: credit flow on */
	info[4] = 0x00;			/* priority */
	info[5] = 0x00;			/* ack_timer */
	info[6] = (uint8_t)(mtu & 0xff);
	info[7] = (uint8_t)(mtu >> 8);
	info[8] = 0x00;			/* max_retrans */
	info[9] = 0x00;			/* credits */

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

static void
inject_msc_resp(struct l2cap_channel *chan, int dlci)
{
	uint8_t info[4];

	info[0] = 0xE1;			/* MCC type: MSC, cr=0 (response), EA=1 */
	info[1] = (2 << 1) | 1;		/* MCC len = 2 */
	info[2] = (uint8_t)RFCOMM_MKADDRESS(1, dlci);
	info[3] = 0x8D;			/* EA=1, DV|RTC|RTR */

	inject_uih(chan, "UIH(0) MSC response", info, 4);
}

#define CHAN_A	3			/* server channel A -> DLCI 6 */
#define CHAN_B	4			/* server channel B -> DLCI 8 */
#define DLCI_A	(CHAN_A * 2)
#define DLCI_B	(CHAN_B * 2)

static int pool_ready;

/*
 * Drive one freshly-connected DLC all the way to OPEN via the production
 * session state machine.  Returns after MSC exchange; mcc timer stopped.
 */
static void
dance(struct l2cap_channel *chan, int dlci)
{
	inject_pn_resp(chan, dlci, 127);
	inject_ctrl(chan, "UA(dlci) [dlc up]", (uint8_t)RFCOMM_MKADDRESS(1, dlci),
	    RFCOMM_MKCONTROL(RFCOMM_FRAME_UA, 1));
	inject_msc_resp(chan, dlci);
}

static int
fire5748(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;
	struct rfcomm_dlc *a = NULL, *b = NULL;
	struct l2cap_channel *chan;
	struct sockaddr_bt dest;

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

	kprintf("=== DF-5748 harness: dead callout_active()/RFCOMM_DLC_DETACH guard ===\n");

	if (!pool_ready) {
		rfcomm_init();
		pool_ready = 1;
	}

	/* two app DLCs to the same destination -> same session */
	err = rfcomm_attach(&a, &upper_proto, NULL);
	KKASSERT(err == 0);
	rfcomm_rcvd(a, 4096);
	err = rfcomm_attach(&b, &upper_proto, NULL);
	KKASSERT(err == 0);
	rfcomm_rcvd(b, 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 = CHAN_A;
	err = rfcomm_connect(a, &dest);
	KKASSERT(err == 0);
	dest.bt_channel = CHAN_B;
	err = rfcomm_connect(b, &dest);
	KKASSERT(err == 0);

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

	/* L2CAP up -> session sends SABM(0) */
	kprintf("[harness] == L2CAP connected ==\n");
	(*chan->lc_proto->connected)(chan->lc_upper);

	/* peer: UA(0) -> session OPEN -> PN commands for both DLCs */
	inject_ctrl(chan, "UA(0) [session up]", 0x03,
	    RFCOMM_MKCONTROL(RFCOMM_FRAME_UA, 1));

	/* peer completes both DLC setups */
	dance(chan, DLCI_A);
	dance(chan, DLCI_B);

	kprintf("[DF-5748] dlc A state=%d (6=OPEN), dlc B state=%d (6=OPEN)\n",
	    a->rd_state, b->rd_state);
	KKASSERT(a->rd_state == RFCOMM_DLC_OPEN);
	KKASSERT(b->rd_state == RFCOMM_DLC_OPEN);

	/*
	 * Local app closes both: OPEN + empty txbuf -> DISC sent,
	 * 20 s ack timer ARMED on each DLC (rfcomm_upper.c:249-253).
	 */
	err = rfcomm_disconnect(a, 0);
	kprintf("[DF-5748] disconnect(A) -> %d; state=%d (7=WAIT_DISCONNECT)\n",
	    err, a->rd_state);
	err = rfcomm_disconnect(b, 0);
	kprintf("[DF-5748] disconnect(B) -> %d; state=%d\n", err, b->rd_state);

	kprintf("[DF-5748] A timer: pending=%d active=%d   (ARMED by "
	    "rfcomm_disconnect:252)\n",
	    callout_pending(&a->rd_timeout), callout_active(&a->rd_timeout));
	KKASSERT(callout_active(&a->rd_timeout) == 1);

	/*
	 * The exact call rfcomm_detach() makes at rfcomm_upper.c:278-279
	 * for a non-CLOSED DLC.  Inside, rfcomm_dlc_close() runs
	 * callout_stop() (rfcomm_dlc.c:164).
	 */
	rfcomm_dlc_close(a, 0);
	kprintf("[DF-5748] A after rfcomm_dlc_close(): pending=%d active=%d\n",
	    callout_pending(&a->rd_timeout), callout_active(&a->rd_timeout));

	if (callout_active(&a->rd_timeout) == 0)
		kprintf("[DF-5748] DEAD: callout_active() == 0 after "
		    "rfcomm_dlc_close(); the RFCOMM_DLC_DETACH branch at "
		    "rfcomm_upper.c:293 is UNREACHABLE\n");

	/* full production detach on B (close + guard + immediate kfree) */
	rfcomm_detach(&b);
	kprintf("[DF-5748] rfcomm_detach(B) returned; B freed via immediate "
	    "kfree (DETACH flag never taken)\n");

	/* release A exactly as detach's tail would */
	a->rd_upper = NULL;
	kfree(a, M_BLUETOOTH);

	kprintf("[DF-5748] session left empty -> 20 s expiry callout will "
	    "rfcomm_session_free() it; sleep to let it fire cleanly\n");
	tsleep(fire5748, 0, "df5748", 22 * hz);
	kprintf("=== DF-5748 harness done (no crash) ===\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, df5748_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fire5748, "I", "fire DF-5748 dead-callout-guard scenario");

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

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

DECLARE_MODULE(rfcomm_up5748, harness_moddata, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);