diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 036d887dec34..a1d249f42be7 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3045,13 +3045,24 @@ static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, u8 code, return NULL; } -static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, - unsigned long *val) +static inline int l2cap_get_conf_opt(void **ptr, void *end, int *type, + int *olen, unsigned long *val) { struct l2cap_conf_opt *opt = *ptr; int len; + /* opt->len is attacker-controlled. Validate that the full option + * (header + value) actually fits in the buffer before touching + * opt->val, otherwise the switch below reads past the end of the + * caller's buffer. + */ + if (end - *ptr < L2CAP_CONF_OPT_SIZE) + return -EINVAL; + len = L2CAP_CONF_OPT_SIZE + opt->len; + if (end - *ptr < len) + return -EINVAL; + *ptr += len; *type = opt->type; @@ -3423,6 +3434,7 @@ static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data, size_t data void *ptr = rsp->data; void *endptr = data + data_size; void *req = chan->conf_req; + void *req_end = req + chan->conf_len; int len = chan->conf_len; int type, hint, olen; unsigned long val; @@ -3436,9 +3448,11 @@ static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data, size_t data BT_DBG("chan %p", chan); while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&req, &type, &olen, &val); - if (len < 0) + int ret = l2cap_get_conf_opt(&req, req_end, &type, &olen, &val); + + if (ret < 0) break; + len -= ret; hint = type & L2CAP_CONF_HINT; type &= L2CAP_CONF_MASK; @@ -3666,6 +3680,7 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, struct l2cap_conf_req *req = data; void *ptr = req->data; void *endptr = data + size; + void *rsp_end = rsp + len; int type, olen; unsigned long val; struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC }; @@ -3674,9 +3689,11 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, BT_DBG("chan %p, rsp %p, len %d, req %p", chan, rsp, len, data); while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); - if (len < 0) + int ret = l2cap_get_conf_opt(&rsp, rsp_end, &type, &olen, &val); + + if (ret < 0) break; + len -= ret; switch (type) { case L2CAP_CONF_MTU: @@ -3927,6 +3944,7 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len) { int type, olen; unsigned long val; + void *rsp_end = rsp + len; /* Use sane default values in case a misbehaving remote device * did not send an RFC or extended window size option. */ @@ -3945,9 +3963,11 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len) return; while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); - if (len < 0) + int ret = l2cap_get_conf_opt(&rsp, rsp_end, &type, &olen, &val); + + if (ret < 0) break; + len -= ret; switch (type) { case L2CAP_CONF_RFC: