Bluetooth: Properly check L2CAP config option output buffer length

upstream a8149a65c1db9c3980873a32e4a96331b7a61f5b from
https://github.com/LineageOS/android_kernel_samsung_msm8974.git

Validate the output buffer length for L2CAP config requests and responses
to avoid overflowing the stack buffer used for building the option blocks.

Change-Id: I7a0ff0b9dd0156c0e6383214a9c86e4ec4c0d236
Cc: stable@vger.kernel.org
Signed-off-by: Ben Seri <ben@armis.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
CVE-2017-1000251
Signed-off-by: Kevin F. Haggerty <haggertk@lineageos.org>
This commit is contained in:
Ben Seri 2017-09-09 23:15:59 +02:00 committed by Artem Borisov
parent 4315ddc3a6
commit bcd33ae876
3 changed files with 50 additions and 41 deletions

View file

@ -670,7 +670,7 @@ void l2cap_cleanup_sockets(void);
u8 l2cap_get_ident(struct l2cap_conn *conn);
void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, void *data);
int l2cap_build_conf_req(struct sock *sk, void *data);
int l2cap_build_conf_req(struct sock *sk, void *data, size_t data_size);
int __l2cap_wait_ack(struct sock *sk);
struct sk_buff *l2cap_create_connless_pdu(struct sock *sk, struct msghdr *msg, size_t len);

View file

@ -926,7 +926,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn)
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf), buf);
l2cap_build_conf_req(sk, buf, sizeof(buf)), buf);
l2cap_pi(sk)->num_conf_req++;
}
@ -2915,12 +2915,15 @@ static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned
return len;
}
static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val)
static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val, size_t size)
{
struct l2cap_conf_opt *opt = *ptr;
BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val);
if (size < L2CAP_CONF_OPT_SIZE + len)
return;
opt->type = type;
opt->len = len;
@ -3304,12 +3307,13 @@ static void l2cap_get_ertm_timeouts(struct l2cap_conf_rfc *rfc,
}
}
int l2cap_build_conf_req(struct sock *sk, void *data)
int l2cap_build_conf_req(struct sock *sk, void *data, size_t data_size)
{
struct l2cap_pinfo *pi = l2cap_pi(sk);
struct l2cap_conf_req *req = data;
struct l2cap_conf_rfc rfc = { .mode = pi->mode };
void *ptr = req->data;
void *endptr = data + data_size;
BT_DBG("sk %p mode %d", sk, pi->mode);
@ -3330,7 +3334,7 @@ int l2cap_build_conf_req(struct sock *sk, void *data)
done:
if (pi->imtu != L2CAP_DEFAULT_MTU)
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu, endptr - ptr);
switch (pi->mode) {
case L2CAP_MODE_BASIC:
@ -3344,7 +3348,7 @@ done:
rfc.max_pdu_size = 0;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
(unsigned long) &rfc);
(unsigned long) &rfc, endptr - ptr);
break;
case L2CAP_MODE_ERTM:
@ -3361,12 +3365,12 @@ done:
rfc.max_pdu_size = cpu_to_le16(pi->imtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
(unsigned long) &rfc);
(unsigned long) &rfc, endptr - ptr);
if ((pi->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW) &&
pi->extended_control) {
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2,
pi->tx_win);
pi->tx_win, endptr - ptr);
}
if (pi->amp_id) {
@ -3374,7 +3378,7 @@ done:
struct l2cap_conf_ext_fs fs = {1, 1, 0xFFFF,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_FS,
sizeof(fs), (unsigned long) &fs);
sizeof(fs), (unsigned long) &fs, endptr - ptr);
}
if (!(pi->conn->feat_mask & L2CAP_FEAT_FCS))
@ -3383,7 +3387,7 @@ done:
if (pi->fcs == L2CAP_FCS_NONE ||
pi->conf_state & L2CAP_CONF_NO_FCS_RECV) {
pi->fcs = L2CAP_FCS_NONE;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs, endptr - ptr);
}
break;
@ -3398,11 +3402,11 @@ done:
rfc.max_pdu_size = cpu_to_le16(pi->imtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
(unsigned long) &rfc);
(unsigned long) &rfc, endptr - ptr);
if ((pi->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW) &&
pi->extended_control) {
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2, 0);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2, 0, endptr - ptr);
}
if (!(pi->conn->feat_mask & L2CAP_FEAT_FCS))
@ -3411,7 +3415,7 @@ done:
if (pi->fcs == L2CAP_FCS_NONE ||
pi->conf_state & L2CAP_CONF_NO_FCS_RECV) {
pi->fcs = L2CAP_FCS_NONE;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs, endptr - ptr);
}
break;
}
@ -3423,12 +3427,13 @@ done:
}
static int l2cap_build_amp_reconf_req(struct sock *sk, void *data)
static int l2cap_build_amp_reconf_req(struct sock *sk, void *data, size_t data_size)
{
struct l2cap_pinfo *pi = l2cap_pi(sk);
struct l2cap_conf_req *req = data;
struct l2cap_conf_rfc rfc = { .mode = pi->mode };
void *ptr = req->data;
void *endptr = data + data_size;
BT_DBG("sk %p", sk);
@ -3449,7 +3454,7 @@ static int l2cap_build_amp_reconf_req(struct sock *sk, void *data)
}
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc),
(unsigned long) &rfc);
(unsigned long) &rfc, endptr - ptr);
if (pi->conn->feat_mask & L2CAP_FEAT_FCS) {
/* TODO assign fcs for br/edr based on socket config option */
@ -3460,7 +3465,7 @@ static int l2cap_build_amp_reconf_req(struct sock *sk, void *data)
else
pi->local_conf.fcs = L2CAP_FCS_CRC16;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->local_conf.fcs);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->local_conf.fcs, endptr - ptr);
pi->fcs = pi->local_conf.fcs | pi->remote_conf.fcs;
}
@ -3470,11 +3475,12 @@ static int l2cap_build_amp_reconf_req(struct sock *sk, void *data)
return ptr - data;
}
static int l2cap_parse_conf_req(struct sock *sk, void *data)
static int l2cap_parse_conf_req(struct sock *sk, void *data, size_t data_size)
{
struct l2cap_pinfo *pi = l2cap_pi(sk);
struct l2cap_conf_rsp *rsp = data;
void *ptr = rsp->data;
void *endptr = data + data_size;
void *req = pi->conf_req;
int len = pi->conf_len;
int type, hint, olen;
@ -3597,7 +3603,8 @@ done:
return -ECONNREFUSED;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
sizeof(rfc), (unsigned long) &rfc);
sizeof(rfc), (unsigned long) &rfc,
endptr - ptr);
}
@ -3616,7 +3623,7 @@ done:
pi->omtu = mtu;
pi->conf_state |= L2CAP_CONF_MTU_DONE;
}
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu, endptr - ptr);
switch (rfc.mode) {
case L2CAP_MODE_BASIC:
@ -3634,11 +3641,11 @@ done:
pi->conf_state |= L2CAP_CONF_MODE_DONE;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
sizeof(rfc), (unsigned long) &rfc);
sizeof(rfc), (unsigned long) &rfc, endptr - ptr);
if (pi->conf_state & L2CAP_CONF_LOCKSTEP)
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_FS,
sizeof(fs), (unsigned long) &fs);
sizeof(fs), (unsigned long) &fs, endptr - ptr);
break;
@ -3648,7 +3655,7 @@ done:
pi->conf_state |= L2CAP_CONF_MODE_DONE;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
sizeof(rfc), (unsigned long) &rfc);
sizeof(rfc), (unsigned long) &rfc, endptr - ptr);
break;
@ -3688,11 +3695,12 @@ done:
return ptr - data;
}
static int l2cap_parse_amp_move_reconf_req(struct sock *sk, void *data)
static int l2cap_parse_amp_move_reconf_req(struct sock *sk, void *data, size_t data_size)
{
struct l2cap_pinfo *pi = l2cap_pi(sk);
struct l2cap_conf_rsp *rsp = data;
void *ptr = rsp->data;
void *endptr = data + data_size;
void *req = pi->conf_req;
int len = pi->conf_len;
int type, hint, olen;
@ -3779,13 +3787,13 @@ static int l2cap_parse_amp_move_reconf_req(struct sock *sk, void *data)
BT_DBG("mtu %d omtu %d", mtu, pi->omtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu, endptr - ptr);
/* Don't allow extended transmit window to change. */
if (tx_win != pi->remote_tx_win) {
result = L2CAP_CONF_UNACCEPT;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2,
pi->remote_tx_win);
pi->remote_tx_win, endptr - ptr);
}
pi->remote_mps = rfc.max_pdu_size;
@ -3798,7 +3806,7 @@ static int l2cap_parse_amp_move_reconf_req(struct sock *sk, void *data)
}
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
sizeof(rfc), (unsigned long) &rfc);
sizeof(rfc), (unsigned long) &rfc, endptr - ptr);
}
if (result != L2CAP_CONF_SUCCESS)
@ -3817,11 +3825,12 @@ done:
return ptr - data;
}
static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data, u16 *result)
static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data, size_t size, u16 *result)
{
struct l2cap_pinfo *pi = l2cap_pi(sk);
struct l2cap_conf_req *req = data;
void *ptr = req->data;
void *endptr = data + size;
int type, olen;
unsigned long val;
struct l2cap_conf_rfc rfc;
@ -3844,13 +3853,13 @@ static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data,
pi->imtu = L2CAP_DEFAULT_MIN_MTU;
} else
pi->imtu = val;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu, endptr - ptr);
break;
case L2CAP_CONF_FLUSH_TO:
pi->flush_to = val;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO,
2, pi->flush_to);
2, pi->flush_to, endptr - ptr);
break;
case L2CAP_CONF_RFC:
@ -3864,14 +3873,14 @@ static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data,
pi->fcs = 0;
l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC,
sizeof(rfc), (unsigned long) &rfc);
sizeof(rfc), (unsigned long) &rfc, endptr - ptr);
break;
case L2CAP_CONF_EXT_WINDOW:
pi->ack_win = min_t(u16, val, pi->ack_win);
l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW,
2, pi->tx_win);
2, pi->tx_win, endptr - ptr);
break;
default:
@ -4242,7 +4251,7 @@ sendresp:
u8 buf[128];
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf), buf);
l2cap_build_conf_req(sk, buf, sizeof(buf)), buf);
l2cap_pi(sk)->num_conf_req++;
}
@ -4293,7 +4302,7 @@ static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hd
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, req), req);
l2cap_build_conf_req(sk, req, sizeof(req)), req);
l2cap_pi(sk)->num_conf_req++;
break;
@ -4397,9 +4406,9 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr
/* Complete config. */
if (!amp_move_reconf)
len = l2cap_parse_conf_req(sk, rspbuf);
len = l2cap_parse_conf_req(sk, rspbuf, sizeof(rspbuf));
else
len = l2cap_parse_amp_move_reconf_req(sk, rspbuf);
len = l2cap_parse_amp_move_reconf_req(sk, rspbuf, sizeof(rspbuf));
if (len < 0) {
l2cap_send_disconn_req(conn, sk, ECONNRESET);
@ -4448,7 +4457,7 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr
u8 buf[64];
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf), buf);
l2cap_build_conf_req(sk, buf, sizeof(buf)), buf);
l2cap_pi(sk)->num_conf_req++;
}
@ -4540,7 +4549,7 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr
/* throw out any old stored conf requests */
result = L2CAP_CONF_SUCCESS;
len = l2cap_parse_conf_rsp(sk, rsp->data,
len, req, &result);
len, req, sizeof(req), &result);
if (len < 0) {
l2cap_send_disconn_req(conn, sk, ECONNRESET);
goto done;
@ -5340,7 +5349,7 @@ void l2cap_amp_physical_complete(int result, u8 local_id, u8 remote_id,
l2cap_send_cmd(pi->conn,
l2cap_get_ident(pi->conn),
L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf), buf);
l2cap_build_conf_req(sk, buf, sizeof(buf)), buf);
l2cap_pi(sk)->num_conf_req++;
}
} else {
@ -6902,7 +6911,7 @@ static int l2cap_amp_move_reconf(struct sock *sk)
pi = l2cap_pi(sk);
l2cap_send_cmd(pi->conn, l2cap_get_ident(pi->conn), L2CAP_CONF_REQ,
l2cap_build_amp_reconf_req(sk, buf), buf);
l2cap_build_amp_reconf_req(sk, buf, sizeof(buf)), buf);
return err;
}
@ -7637,7 +7646,7 @@ static int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn),
L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf),
l2cap_build_conf_req(sk, buf, sizeof(buf)),
buf);
l2cap_pi(sk)->num_conf_req++;
}

View file

@ -1035,7 +1035,7 @@ static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct ms
l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ,
l2cap_build_conf_req(sk, buf), buf);
l2cap_build_conf_req(sk, buf, sizeof(buf)), buf);
l2cap_pi(sk)->num_conf_req++;
release_sock(sk);