CVE-2017-1000251 (C, published 2017-09-12T17:29Z)
Description: value: The native Bluetooth stack in the Linux Kernel (BlueZ), starting at the Linux kernel version 2.6.32 and up to and including 4.13.1, are vulnerable to a stack overflow vulnerability in the processing of L2CAP configuration responses resulting in Remote code execution in kernel space.
Severity: HIGH (CVSS v3 base score: 8)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/torvalds/linux
Files changed:
include/net/bluetooth/l2cap.h
net/bluetooth/l2cap.c
Commit message: Bluetooth: Add configuration support for ERTM and Streaming mode

Add support to config_req and config_rsp to configure ERTM and Streaming
mode. If the remote device specifies ERTM or Streaming mode, then the
same mode is proposed. Otherwise ERTM or Basic mode is used. And in case
of a state 2 device, the remote device should propose the same mode. If
not, then the channel gets disconnected.

Signed-off-by: Gustavo F. Padovan <gustavo@las.ic.unicamp.br>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>


Vulnerable code:
#define L2CAP_DEFAULT_RX_WINDOW		1
	__u8		conf_retry;
#define L2CAP_CONF_REQ_SENT	0x01
#define L2CAP_CONF_INPUT_DONE	0x02
#define L2CAP_CONF_OUTPUT_DONE	0x04
#define L2CAP_CONF_CONNECT_PEND	0x80
#define L2CAP_CONF_MAX_RETRIES	2
	struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC };
		rfc.txwin_size      = L2CAP_DEFAULT_RX_WINDOW;
		rfc.retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO);
		rfc.monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO);
		if (rfc.mode == L2CAP_MODE_BASIC) {
			if (mtu < pi->omtu)
				result = L2CAP_CONF_UNACCEPT;
			else {
				pi->omtu = mtu;
				pi->conf_state |= L2CAP_CONF_OUTPUT_DONE;
			}
			l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
		} else {
			rfc.mode = L2CAP_MODE_BASIC;
			l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
		}
	}
	if (len < 0)
		if (++l2cap_pi(sk)->conf_retry < L2CAP_CONF_MAX_RETRIES) {
			char req[128];
			/* It does not make sense to adjust L2CAP parameters
			 * that are currently defined in the spec. We simply
			 * resend config request that we sent earlier. It is
			 * stupid, but it helps qualification testing which
			 * expects at least some response from us. */
			l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
						l2cap_build_conf_req(sk, req), req);
			goto done;

Fixed code:
#define L2CAP_DEFAULT_MIN_MTU		48
#define L2CAP_DEFAULT_TX_WINDOW		1
	__u8		num_conf_req;
	__u8		num_conf_rsp;

	__u8		remote_tx_win;
	__u8		remote_max_tx;
	__u16		retrans_timeout;
	__u16		monitor_timeout;
	__u16		max_pdu_size;

#define L2CAP_CONF_REQ_SENT       0x01
#define L2CAP_CONF_INPUT_DONE     0x02
#define L2CAP_CONF_OUTPUT_DONE    0x04
#define L2CAP_CONF_MTU_DONE       0x08
#define L2CAP_CONF_MODE_DONE      0x10
#define L2CAP_CONF_CONNECT_PEND   0x20
#define L2CAP_CONF_STATE2_DEVICE  0x80

#define L2CAP_CONF_MAX_CONF_REQ 2
#define L2CAP_CONF_MAX_CONF_RSP 2
	case L2CAP_MODE_STREAMING:
	case L2CAP_MODE_STREAMING:
static int l2cap_mode_supported(__u8 mode, __u32 feat_mask)
{
	u32 local_feat_mask = l2cap_feat_mask;
	if (enable_ertm)
		local_feat_mask |= L2CAP_FEAT_ERTM;

	switch (mode) {
	case L2CAP_MODE_ERTM:
		return L2CAP_FEAT_ERTM & feat_mask & local_feat_mask;
	case L2CAP_MODE_STREAMING:
		return L2CAP_FEAT_STREAMING & feat_mask & local_feat_mask;
	default:
		return 0x00;
	}
}

static inline __u8 l2cap_select_mode(__u8 mode, __u16 remote_feat_mask)
{
	switch (mode) {
	case L2CAP_MODE_STREAMING:
	case L2CAP_MODE_ERTM:
		if (l2cap_mode_supported(mode, remote_feat_mask))
			return mode;
		/* fall through */
	default:
		return L2CAP_MODE_BASIC;
	}
}

	struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_ERTM };
	if (pi->num_conf_req || pi->num_conf_rsp)
		goto done;

	switch (pi->mode) {
	case L2CAP_MODE_STREAMING:
	case L2CAP_MODE_ERTM:
		pi->conf_state |= L2CAP_CONF_STATE2_DEVICE;
		if (!l2cap_mode_supported(pi->mode, pi->conn->feat_mask)) {
			struct l2cap_disconn_req req;
			req.dcid = cpu_to_le16(pi->dcid);
			req.scid = cpu_to_le16(pi->scid);
			l2cap_send_cmd(pi->conn, l2cap_get_ident(pi->conn),
					L2CAP_DISCONN_REQ, sizeof(req), &req);
		}
		break;
	default:
		pi->mode = l2cap_select_mode(rfc.mode, pi->conn->feat_mask);
		break;
	}

done:
		rfc.txwin_size      = L2CAP_DEFAULT_TX_WINDOW;
		rfc.retrans_timeout = 0;
		rfc.monitor_timeout = 0;
		rfc.max_pdu_size    = cpu_to_le16(L2CAP_DEFAULT_MAX_RX_APDU);

		l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
					sizeof(rfc), (unsigned long) &rfc);
		break;

	case L2CAP_MODE_STREAMING:
		rfc.mode            = L2CAP_MODE_STREAMING;
		rfc.txwin_size      = 0;
		rfc.max_transmit    = 0;
		rfc.retrans_timeout = 0;
		rfc.monitor_timeout = 0;
	if (pi->num_conf_rsp || pi->num_conf_req)
		goto done;

	switch (pi->mode) {
	case L2CAP_MODE_STREAMING:
	case L2CAP_MODE_ERTM:
		pi->conf_state |= L2CAP_CONF_STATE2_DEVICE;
		if (!l2cap_mode_supported(pi->mode, pi->conn->feat_mask))
			return -ECONNREFUSED;
		break;
	default:
		pi->mode = l2cap_select_mode(rfc.mode, pi->conn->feat_mask);
		break;
	}

done:
	if (pi->mode != rfc.mode) {
		result = L2CAP_CONF_UNACCEPT;
		rfc.mode = pi->mode;

		if (pi->num_conf_rsp == 1)
			return -ECONNREFUSED;

		l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
					sizeof(rfc), (unsigned long) &rfc);
	}


		if (mtu < L2CAP_DEFAULT_MIN_MTU)
			result = L2CAP_CONF_UNACCEPT;
		else {
			pi->omtu = mtu;
			pi->conf_state |= L2CAP_CONF_MTU_DONE;
		}
		l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
		switch (rfc.mode) {
		case L2CAP_MODE_BASIC:
			pi->fcs = L2CAP_FCS_NONE;
			pi->conf_state |= L2CAP_CONF_MODE_DONE;
			break;

		case L2CAP_MODE_ERTM:
			pi->remote_tx_win = rfc.txwin_size;
			pi->remote_max_tx = rfc.max_transmit;
			pi->max_pdu_size = rfc.max_pdu_size;

			rfc.retrans_timeout = L2CAP_DEFAULT_RETRANS_TO;
			rfc.monitor_timeout = L2CAP_DEFAULT_MONITOR_TO;

			pi->conf_state |= L2CAP_CONF_MODE_DONE;
			break;

		case L2CAP_MODE_STREAMING:
			pi->remote_tx_win = rfc.txwin_size;
			pi->max_pdu_size = rfc.max_pdu_size;

			pi->conf_state |= L2CAP_CONF_MODE_DONE;
			break;

		default:
			rfc.mode = pi->mode;
		}
		l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
		if (result == L2CAP_CONF_SUCCESS)
			pi->conf_state |= L2CAP_CONF_OUTPUT_DONE;
	}
static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data, u16 *result)
{
	struct l2cap_pinfo *pi = l2cap_pi(sk);
	struct l2cap_conf_req *req = data;
	void *ptr = req->data;
	int type, olen;
	unsigned long val;
	struct l2cap_conf_rfc rfc;

	BT_DBG("sk %p, rsp %p, len %d, req %p", sk, rsp, len, data);

	while (len >= L2CAP_CONF_OPT_SIZE) {
		len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val);

		switch (type) {
		case L2CAP_CONF_MTU:
			if (val < L2CAP_DEFAULT_MIN_MTU) {
				*result = L2CAP_CONF_UNACCEPT;
				pi->omtu = L2CAP_DEFAULT_MIN_MTU;
			} else
				pi->omtu = val;
			l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
			break;

		case L2CAP_CONF_FLUSH_TO:
			pi->flush_to = val;
			l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO,
							2, pi->flush_to);
			break;

		case L2CAP_CONF_RFC:
			if (olen == sizeof(rfc))
				memcpy(&rfc, (void *)val, olen);

			if ((pi->conf_state & L2CAP_CONF_STATE2_DEVICE) &&
							rfc.mode != pi->mode)
				return -ECONNREFUSED;

			pi->mode = rfc.mode;
			pi->fcs = 0;

			l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
					sizeof(rfc), (unsigned long) &rfc);
			break;
		}
	}

	if (*result == L2CAP_CONF_SUCCESS) {
		switch (rfc.mode) {
		case L2CAP_MODE_ERTM:
			pi->remote_tx_win   = rfc.txwin_size;
			pi->retrans_timeout = rfc.retrans_timeout;
			pi->monitor_timeout = rfc.monitor_timeout;
			pi->max_pdu_size    = le16_to_cpu(rfc.max_pdu_size);
			break;
		case L2CAP_MODE_STREAMING:
			pi->max_pdu_size    = le16_to_cpu(rfc.max_pdu_size);
			break;
		}
	}

	req->dcid   = cpu_to_le16(pi->dcid);
	req->flags  = cpu_to_le16(0x0000);

	return ptr - data;
}

		l2cap_pi(sk)->num_conf_req++;
	if (len < 0) {
		struct l2cap_disconn_req req;
		req.dcid = cpu_to_le16(l2cap_pi(sk)->dcid);
		req.scid = cpu_to_le16(l2cap_pi(sk)->scid);
		l2cap_send_cmd(conn, l2cap_get_ident(conn),
					L2CAP_DISCONN_REQ, sizeof(req), &req);
	}
	l2cap_pi(sk)->num_conf_rsp++;
		l2cap_pi(sk)->num_conf_req++;
		if (l2cap_pi(sk)->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) {
			int len = cmd->len - sizeof(*rsp);
			char req[64];

			/* throw out any old stored conf requests */
			result = L2CAP_CONF_SUCCESS;
			len = l2cap_parse_conf_rsp(sk, rsp->data,
							len, req, &result);
			if (len < 0) {
				struct l2cap_disconn_req req;
				req.dcid = cpu_to_le16(l2cap_pi(sk)->dcid);
				req.scid = cpu_to_le16(l2cap_pi(sk)->scid);
				l2cap_send_cmd(conn, l2cap_get_ident(conn),
					L2CAP_DISCONN_REQ, sizeof(req), &req);
				goto done;
			}

			l2cap_send_cmd(conn, l2cap_get_ident(conn),
						L2CAP_CONF_REQ, len, req);
			l2cap_pi(sk)->num_conf_req++;
			if (result != L2CAP_CONF_SUCCESS)
				goto done;
			break;