From d69a7047bbd96cb9ced072d4a976685c97d09356 Mon Sep 17 00:00:00 2001 From: Ben Seri Date: Sat, 9 Sep 2017 23:15:59 +0200 Subject: [PATCH] Bluetooth: Properly check L2CAP config option output buffer length 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 Signed-off-by: Marcel Holtmann Signed-off-by: Linus Torvalds CVE-2017-1000251 Signed-off-by: Kevin F. Haggerty --- include/net/bluetooth/l2cap.h | 2 +- net/bluetooth/l2cap_core.c | 87 +++++++++++++++++++---------------- net/bluetooth/l2cap_sock.c | 2 +- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 9c2b735c898..8e281b41588 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -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); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index a55416043e0..aba12f242cd 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -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++; } @@ -2923,12 +2923,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; @@ -3312,12 +3315,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); @@ -3338,7 +3342,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: @@ -3352,7 +3356,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: @@ -3369,12 +3373,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) { @@ -3382,7 +3386,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)) @@ -3391,7 +3395,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; @@ -3406,11 +3410,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)) @@ -3419,7 +3423,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; } @@ -3431,12 +3435,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); @@ -3457,7 +3462,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 */ @@ -3468,7 +3473,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; } @@ -3478,11 +3483,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; @@ -3605,7 +3611,8 @@ done: return -ECONNREFUSED; l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + sizeof(rfc), (unsigned long) &rfc, + endptr - ptr); } @@ -3624,7 +3631,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: @@ -3642,11 +3649,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; @@ -3656,7 +3663,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; @@ -3696,11 +3703,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; @@ -3787,13 +3795,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; @@ -3806,7 +3814,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) @@ -3825,11 +3833,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; @@ -3852,13 +3861,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: @@ -3872,14 +3881,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: @@ -4262,7 +4271,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++; } @@ -4320,7 +4329,7 @@ static inline int l2cap_connect_rsp(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, req), req); + l2cap_build_conf_req(sk, req, sizeof(req)), req); l2cap_pi(sk)->num_conf_req++; break; @@ -4427,9 +4436,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); @@ -4478,7 +4487,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++; } @@ -4575,7 +4584,7 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn, /* 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; @@ -5397,7 +5406,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 { @@ -6959,7 +6968,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; } @@ -7694,7 +7703,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++; } diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 6da494fd85a..baf1af7b0c0 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1036,7 +1036,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);