]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
Bluetooth: L2CAP: Fix div-by-zero in l2cap_le_flowctl_init()
authorSungwoo Kim <iam@sung-woo.kim>
Sat, 4 May 2024 19:23:29 +0000 (15:23 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 25 May 2024 14:28:38 +0000 (16:28 +0200)
commit a5b862c6a221459d54e494e88965b48dcfa6cc44 upstream.

l2cap_le_flowctl_init() can cause both div-by-zero and an integer
overflow since hdev->le_mtu may not fall in the valid range.

Move MTU from hci_dev to hci_conn to validate MTU and stop the connection
process earlier if MTU is invalid.
Also, add a missing validation in read_buffer_size() and make it return
an error value if the validation fails.
Now hci_conn_add() returns ERR_PTR() as it can fail due to the both a
kzalloc failure and invalid MTU value.

divide error: 0000 [#1] PREEMPT SMP KASAN NOPTI
CPU: 0 PID: 67 Comm: kworker/u5:0 Tainted: G        W          6.9.0-rc5+ #20
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
Workqueue: hci0 hci_rx_work
RIP: 0010:l2cap_le_flowctl_init+0x19e/0x3f0 net/bluetooth/l2cap_core.c:547
Code: e8 17 17 0c 00 66 41 89 9f 84 00 00 00 bf 01 00 00 00 41 b8 02 00 00 00 4c
89 fe 4c 89 e2 89 d9 e8 27 17 0c 00 44 89 f0 31 d2 <66> f7 f3 89 c3 ff c3 4d 8d
b7 88 00 00 00 4c 89 f0 48 c1 e8 03 42
RSP: 0018:ffff88810bc0f858 EFLAGS: 00010246
RAX: 00000000000002a0 RBX: 0000000000000000 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: ffff88810bc0f7c0 RDI: ffffc90002dcb66f
RBP: ffff88810bc0f880 R08: aa69db2dda70ff01 R09: 0000ffaaaaaaaaaa
R10: 0084000000ffaaaa R11: 0000000000000000 R12: ffff88810d65a084
R13: dffffc0000000000 R14: 00000000000002a0 R15: ffff88810d65a000
FS:  0000000000000000(0000) GS:ffff88811ac00000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000020000100 CR3: 0000000103268003 CR4: 0000000000770ef0
PKRU: 55555554
Call Trace:
 <TASK>
 l2cap_le_connect_req net/bluetooth/l2cap_core.c:4902 [inline]
 l2cap_le_sig_cmd net/bluetooth/l2cap_core.c:5420 [inline]
 l2cap_le_sig_channel net/bluetooth/l2cap_core.c:5486 [inline]
 l2cap_recv_frame+0xe59d/0x11710 net/bluetooth/l2cap_core.c:6809
 l2cap_recv_acldata+0x544/0x10a0 net/bluetooth/l2cap_core.c:7506
 hci_acldata_packet net/bluetooth/hci_core.c:3939 [inline]
 hci_rx_work+0x5e5/0xb20 net/bluetooth/hci_core.c:4176
 process_one_work kernel/workqueue.c:3254 [inline]
 process_scheduled_works+0x90f/0x1530 kernel/workqueue.c:3335
 worker_thread+0x926/0xe70 kernel/workqueue.c:3416
 kthread+0x2e3/0x380 kernel/kthread.c:388
 ret_from_fork+0x5c/0x90 arch/x86/kernel/process.c:147
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:244
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---

Fixes: 6ed58ec520ad ("Bluetooth: Use LE buffers for LE traffic")
Suggested-by: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Signed-off-by: Sungwoo Kim <iam@sung-woo.kim>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/net/bluetooth/hci.h
include/net/bluetooth/hci_core.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_event.c
net/bluetooth/iso.c
net/bluetooth/l2cap_core.c
net/bluetooth/sco.c

index 2cfd8d862639f6983c74a656eb795fcd61cb1f2d..f9db2d1ca5d3701b11aca286b52f2c1e69918ce8 100644 (file)
@@ -1665,6 +1665,15 @@ struct hci_cp_le_set_event_mask {
        __u8     mask[8];
 } __packed;
 
+/* BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 4, Part E
+ * 7.8.2 LE Read Buffer Size command
+ * MAX_LE_MTU is 0xffff.
+ * 0 is also valid. It means that no dedicated LE Buffer exists.
+ * It should use the HCI_Read_Buffer_Size command and mtu is shared
+ * between BR/EDR and LE.
+ */
+#define HCI_MIN_LE_MTU 0x001b
+
 #define HCI_OP_LE_READ_BUFFER_SIZE     0x2002
 struct hci_rp_le_read_buffer_size {
        __u8     status;
index fe9e1524d30ff60f621adfca0227f9c3318c7d07..8504e10f51700c4e3d445f1cba21237d039a3342 100644 (file)
@@ -706,6 +706,7 @@ struct hci_conn {
        __u16           handle;
        __u16           sync_handle;
        __u16           state;
+       __u16           mtu;
        __u8            mode;
        __u8            type;
        __u8            role;
index 18f97b2288699f4e6c34a3e89bcf5e4590a67c73..9a369bc14fd576638bcaecb4c41ca633f993d146 100644 (file)
@@ -909,11 +909,37 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
 {
        struct hci_conn *conn;
 
+       switch (type) {
+       case ACL_LINK:
+               if (!hdev->acl_mtu)
+                       return ERR_PTR(-ECONNREFUSED);
+               break;
+       case ISO_LINK:
+               if (hdev->iso_mtu)
+                       /* Dedicated ISO Buffer exists */
+                       break;
+               fallthrough;
+       case LE_LINK:
+               if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU)
+                       return ERR_PTR(-ECONNREFUSED);
+               if (!hdev->le_mtu && hdev->acl_mtu < HCI_MIN_LE_MTU)
+                       return ERR_PTR(-ECONNREFUSED);
+               break;
+       case SCO_LINK:
+       case ESCO_LINK:
+               if (!hdev->sco_pkts)
+                       /* Controller does not support SCO or eSCO over HCI */
+                       return ERR_PTR(-ECONNREFUSED);
+               break;
+       default:
+               return ERR_PTR(-ECONNREFUSED);
+       }
+
        bt_dev_dbg(hdev, "dst %pMR handle 0x%4.4x", dst, handle);
 
        conn = kzalloc(sizeof(*conn), GFP_KERNEL);
        if (!conn)
-               return NULL;
+               return ERR_PTR(-ENOMEM);
 
        bacpy(&conn->dst, dst);
        bacpy(&conn->src, &hdev->bdaddr);
@@ -944,10 +970,12 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
        switch (type) {
        case ACL_LINK:
                conn->pkt_type = hdev->pkt_type & ACL_PTYPE_MASK;
+               conn->mtu = hdev->acl_mtu;
                break;
        case LE_LINK:
                /* conn->src should reflect the local identity address */
                hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
+               conn->mtu = hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu;
                break;
        case ISO_LINK:
                /* conn->src should reflect the local identity address */
@@ -959,6 +987,8 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
                else if (conn->role == HCI_ROLE_MASTER)
                        conn->cleanup = cis_cleanup;
 
+               conn->mtu = hdev->iso_mtu ? hdev->iso_mtu :
+                           hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu;
                break;
        case SCO_LINK:
                if (lmp_esco_capable(hdev))
@@ -966,9 +996,12 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
                                        (hdev->esco_type & EDR_ESCO_MASK);
                else
                        conn->pkt_type = hdev->pkt_type & SCO_PTYPE_MASK;
+
+               conn->mtu = hdev->sco_mtu;
                break;
        case ESCO_LINK:
                conn->pkt_type = hdev->esco_type & ~EDR_ESCO_MASK;
+               conn->mtu = hdev->sco_mtu;
                break;
        }
 
@@ -1011,7 +1044,7 @@ struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type,
 
        handle = hci_conn_hash_alloc_unset(hdev);
        if (unlikely(handle < 0))
-               return NULL;
+               return ERR_PTR(-ECONNREFUSED);
 
        return hci_conn_add(hdev, type, dst, role, handle);
 }
@@ -1317,8 +1350,8 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
                bacpy(&conn->dst, dst);
        } else {
                conn = hci_conn_add_unset(hdev, LE_LINK, dst, role);
-               if (!conn)
-                       return ERR_PTR(-ENOMEM);
+               if (IS_ERR(conn))
+                       return conn;
                hci_conn_hold(conn);
                conn->pending_sec_level = sec_level;
        }
@@ -1494,8 +1527,8 @@ static struct hci_conn *hci_add_bis(struct hci_dev *hdev, bdaddr_t *dst,
                return ERR_PTR(-EADDRINUSE);
 
        conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
-       if (!conn)
-               return ERR_PTR(-ENOMEM);
+       if (IS_ERR(conn))
+               return conn;
 
        conn->state = BT_CONNECT;
 
@@ -1538,8 +1571,8 @@ struct hci_conn *hci_connect_le_scan(struct hci_dev *hdev, bdaddr_t *dst,
        BT_DBG("requesting refresh of dst_addr");
 
        conn = hci_conn_add_unset(hdev, LE_LINK, dst, HCI_ROLE_MASTER);
-       if (!conn)
-               return ERR_PTR(-ENOMEM);
+       if (IS_ERR(conn))
+               return conn;
 
        if (hci_explicit_conn_params_set(hdev, dst, dst_type) < 0) {
                hci_conn_del(conn);
@@ -1586,8 +1619,8 @@ struct hci_conn *hci_connect_acl(struct hci_dev *hdev, bdaddr_t *dst,
        acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
        if (!acl) {
                acl = hci_conn_add_unset(hdev, ACL_LINK, dst, HCI_ROLE_MASTER);
-               if (!acl)
-                       return ERR_PTR(-ENOMEM);
+               if (IS_ERR(acl))
+                       return acl;
        }
 
        hci_conn_hold(acl);
@@ -1655,9 +1688,9 @@ struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst,
        sco = hci_conn_hash_lookup_ba(hdev, type, dst);
        if (!sco) {
                sco = hci_conn_add_unset(hdev, type, dst, HCI_ROLE_MASTER);
-               if (!sco) {
+               if (IS_ERR(sco)) {
                        hci_conn_drop(acl);
-                       return ERR_PTR(-ENOMEM);
+                       return sco;
                }
        }
 
@@ -1847,8 +1880,8 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst,
                                       qos->ucast.cis);
        if (!cis) {
                cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
-               if (!cis)
-                       return ERR_PTR(-ENOMEM);
+               if (IS_ERR(cis))
+                       return cis;
                cis->cleanup = cis_cleanup;
                cis->dst_type = dst_type;
                cis->iso_qos.ucast.cig = BT_ISO_QOS_CIG_UNSET;
@@ -1983,14 +2016,8 @@ static void hci_iso_qos_setup(struct hci_dev *hdev, struct hci_conn *conn,
                              struct bt_iso_io_qos *qos, __u8 phy)
 {
        /* Only set MTU if PHY is enabled */
-       if (!qos->sdu && qos->phy) {
-               if (hdev->iso_mtu > 0)
-                       qos->sdu = hdev->iso_mtu;
-               else if (hdev->le_mtu > 0)
-                       qos->sdu = hdev->le_mtu;
-               else
-                       qos->sdu = hdev->acl_mtu;
-       }
+       if (!qos->sdu && qos->phy)
+               qos->sdu = conn->mtu;
 
        /* Use the same PHY as ACL if set to any */
        if (qos->phy == BT_ISO_PHY_ANY)
index 0f56ad33801e3699cd0b9ab53016f57c977909d8..c19d78e5d2053ffe7aba0d020d6f095db3a1d6ab 100644 (file)
@@ -954,6 +954,9 @@ static u8 hci_cc_read_buffer_size(struct hci_dev *hdev, void *data,
        BT_DBG("%s acl mtu %d:%d sco mtu %d:%d", hdev->name, hdev->acl_mtu,
               hdev->acl_pkts, hdev->sco_mtu, hdev->sco_pkts);
 
+       if (!hdev->acl_mtu || !hdev->acl_pkts)
+               return HCI_ERROR_INVALID_PARAMETERS;
+
        return rp->status;
 }
 
@@ -1263,6 +1266,9 @@ static u8 hci_cc_le_read_buffer_size(struct hci_dev *hdev, void *data,
 
        BT_DBG("%s le mtu %d:%d", hdev->name, hdev->le_mtu, hdev->le_pkts);
 
+       if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU)
+               return HCI_ERROR_INVALID_PARAMETERS;
+
        return rp->status;
 }
 
@@ -2342,8 +2348,8 @@ static void hci_cs_create_conn(struct hci_dev *hdev, __u8 status)
                if (!conn) {
                        conn = hci_conn_add_unset(hdev, ACL_LINK, &cp->bdaddr,
                                                  HCI_ROLE_MASTER);
-                       if (!conn)
-                               bt_dev_err(hdev, "no memory for new connection");
+                       if (IS_ERR(conn))
+                               bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn));
                }
        }
 
@@ -3154,8 +3160,8 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
                                                      BDADDR_BREDR)) {
                        conn = hci_conn_add_unset(hdev, ev->link_type,
                                                  &ev->bdaddr, HCI_ROLE_SLAVE);
-                       if (!conn) {
-                               bt_dev_err(hdev, "no memory for new conn");
+                       if (IS_ERR(conn)) {
+                               bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn));
                                goto unlock;
                        }
                } else {
@@ -3343,8 +3349,8 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data,
        if (!conn) {
                conn = hci_conn_add_unset(hdev, ev->link_type, &ev->bdaddr,
                                          HCI_ROLE_SLAVE);
-               if (!conn) {
-                       bt_dev_err(hdev, "no memory for new connection");
+               if (IS_ERR(conn)) {
+                       bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn));
                        goto unlock;
                }
        }
@@ -3821,6 +3827,9 @@ static u8 hci_cc_le_read_buffer_size_v2(struct hci_dev *hdev, void *data,
        BT_DBG("%s acl mtu %d:%d iso mtu %d:%d", hdev->name, hdev->acl_mtu,
               hdev->acl_pkts, hdev->iso_mtu, hdev->iso_pkts);
 
+       if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU)
+               return HCI_ERROR_INVALID_PARAMETERS;
+
        return rp->status;
 }
 
@@ -5912,8 +5921,8 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
                        goto unlock;
 
                conn = hci_conn_add_unset(hdev, LE_LINK, bdaddr, role);
-               if (!conn) {
-                       bt_dev_err(hdev, "no memory for new connection");
+               if (IS_ERR(conn)) {
+                       bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn));
                        goto unlock;
                }
 
@@ -7042,7 +7051,7 @@ static void hci_le_cis_req_evt(struct hci_dev *hdev, void *data,
        if (!cis) {
                cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE,
                                   cis_handle);
-               if (!cis) {
+               if (IS_ERR(cis)) {
                        hci_le_reject_cis(hdev, ev->cis_handle);
                        goto unlock;
                }
@@ -7151,7 +7160,7 @@ static void hci_le_big_sync_established_evt(struct hci_dev *hdev, void *data,
                if (!bis) {
                        bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY,
                                           HCI_ROLE_SLAVE, handle);
-                       if (!bis)
+                       if (IS_ERR(bis))
                                continue;
                }
 
@@ -7223,7 +7232,7 @@ static void hci_le_big_info_adv_report_evt(struct hci_dev *hdev, void *data,
        pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY,
                                     HCI_ROLE_SLAVE);
 
-       if (!pa_sync)
+       if (IS_ERR(pa_sync))
                goto unlock;
 
        pa_sync->sync_handle = le16_to_cpu(ev->sync_handle);
index fa6c2e95d5542226d7e7f273989fb9af3e653fd3..6d217df75c62cdda75f3bd67c476285b30ad77c5 100644 (file)
@@ -1264,7 +1264,7 @@ static int iso_sock_sendmsg(struct socket *sock, struct msghdr *msg,
                return -ENOTCONN;
        }
 
-       mtu = iso_pi(sk)->conn->hcon->hdev->iso_mtu;
+       mtu = iso_pi(sk)->conn->hcon->mtu;
 
        release_sock(sk);
 
index 9223b1a698e3a82a6d9ba5a7e864e78c64a2225b..3f7a82f10fe98d1e45a25dfe8d9973c6ae85be72 100644 (file)
@@ -6241,7 +6241,7 @@ static int l2cap_finish_move(struct l2cap_chan *chan)
        BT_DBG("chan %p", chan);
 
        chan->rx_state = L2CAP_RX_STATE_RECV;
-       chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
+       chan->conn->mtu = chan->conn->hcon->mtu;
 
        return l2cap_resegment(chan);
 }
@@ -6308,7 +6308,7 @@ static int l2cap_rx_state_wait_f(struct l2cap_chan *chan,
         */
        chan->next_tx_seq = control->reqseq;
        chan->unacked_frames = 0;
-       chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
+       chan->conn->mtu = chan->conn->hcon->mtu;
 
        err = l2cap_resegment(chan);
 
@@ -6848,18 +6848,7 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
 
        BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan);
 
-       switch (hcon->type) {
-       case LE_LINK:
-               if (hcon->hdev->le_mtu) {
-                       conn->mtu = hcon->hdev->le_mtu;
-                       break;
-               }
-               fallthrough;
-       default:
-               conn->mtu = hcon->hdev->acl_mtu;
-               break;
-       }
-
+       conn->mtu = hcon->mtu;
        conn->feat_mask = 0;
 
        conn->local_fixed_chan = L2CAP_FC_SIG_BREDR | L2CAP_FC_CONNLESS;
index e0ad30862ee4142cac48d0538fcec9df6e97b8b2..71d36582d4efa3e9dfdb5d696c4f53e12863525e 100644 (file)
@@ -126,7 +126,6 @@ static void sco_sock_clear_timer(struct sock *sk)
 /* ---- SCO connections ---- */
 static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
 {
-       struct hci_dev *hdev = hcon->hdev;
        struct sco_conn *conn = hcon->sco_data;
 
        if (conn) {
@@ -144,9 +143,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
 
        hcon->sco_data = conn;
        conn->hcon = hcon;
+       conn->mtu = hcon->mtu;
 
-       if (hdev->sco_mtu > 0)
-               conn->mtu = hdev->sco_mtu;
+       if (hcon->mtu > 0)
+               conn->mtu = hcon->mtu;
        else
                conn->mtu = 60;