BT_DBG("conn %p", conn);
if (conn->hcon) {
- conn->hcon->iso_data = NULL;
- if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags))
- hci_conn_drop(conn->hcon);
+ spin_lock(&conn->hcon->proto_lock);
+
+ /* Check we are not racing with iso_conn_add */
+ if (conn->hcon->iso_data == conn) {
+ conn->hcon->iso_data = NULL;
+ if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags))
+ hci_conn_drop(conn->hcon);
+ }
+
+ spin_unlock(&conn->hcon->proto_lock);
}
kfree_skb(conn->rx_skb);
BT_DBG("conn %p refcnt %d", conn, kref_read(&conn->ref));
+ /* The following race vs. iso_conn_del() is possible:
+ *
+ * 1. conn->hcon != NULL here
+ * 2. kref_put puts the last reference
+ * 3. concurrent iso_conn_del() gets iso_conn_hold_unless_zero() -> NULL
+ * and returns immediately, so conn->hcon is not cleared
+ * 4. iso_conn_free() dereferences conn->hcon
+ *
+ * To avoid UAF in step 4, take RCU before decrementing the refcount.
+ */
+ rcu_read_lock();
+
kref_put(&conn->ref, iso_conn_free);
+
+ rcu_read_unlock();
}
static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn)
/* ---- ISO connections ---- */
static struct iso_conn *iso_conn_add(struct hci_conn *hcon)
+ __must_hold(&hcon->hdev->lock)
{
- struct iso_conn *conn = hcon->iso_data;
+ struct iso_conn *conn;
+
+ spin_lock(&hcon->proto_lock);
- conn = iso_conn_hold_unless_zero(conn);
+ conn = iso_conn_hold_unless_zero(hcon->iso_data);
if (conn) {
if (!conn->hcon) {
iso_conn_lock(conn);
conn->hcon = hcon;
iso_conn_unlock(conn);
}
+ spin_unlock(&hcon->proto_lock);
return conn;
}
- conn = kzalloc_obj(*conn);
- if (!conn)
+ conn = kzalloc_obj(*conn, GFP_ATOMIC);
+ if (!conn) {
+ spin_unlock(&hcon->proto_lock);
return NULL;
+ }
kref_init(&conn->ref);
spin_lock_init(&conn->lock);
conn->hcon = hcon;
conn->tx_sn = 0;
+ spin_unlock(&hcon->proto_lock);
+
BT_DBG("hcon %p conn %p", hcon, conn);
return conn;
static void iso_conn_del(struct hci_conn *hcon, int err)
__must_hold(&hcon->hdev->lock)
{
- struct iso_conn *conn = hcon->iso_data;
+ struct iso_conn *conn;
struct sock *sk;
- conn = iso_conn_hold_unless_zero(conn);
+ spin_lock(&hcon->proto_lock);
+ conn = iso_conn_hold_unless_zero(hcon->iso_data);
+ spin_unlock(&hcon->proto_lock);
if (!conn)
return;
done:
/* No sk access to conn->hcon any more (lock_sock + hdev->lock) */
+ spin_lock(&hcon->proto_lock);
iso_conn_lock(conn);
conn->hcon = NULL;
hcon->iso_data = NULL;
iso_conn_unlock(conn);
+ spin_unlock(&hcon->proto_lock);
iso_conn_put(conn);
}
iso_pi(sk)->bc_sid = hcon->sid;
}
+ lockdep_assert_held(&hcon->hdev->lock);
+
conn = iso_conn_add(hcon);
if (!conn) {
hci_conn_drop(hcon);
}
}
+ lockdep_assert_held(&hcon->hdev->lock);
+
conn = iso_conn_add(hcon);
if (!conn) {
hci_conn_drop(hcon);
*/
if (bis_sk) {
hcon->state = BT_OPEN;
- hcon->iso_data = NULL;
- iso_pi(sk)->conn->hcon = NULL;
+ set_bit(ISO_CONN_DROPPED, iso_pi(sk)->conn->flags);
+
iso_sock_clear_timer(sk);
iso_chan_del(sk, bt_to_errno(hcon->abort_reason));
sock_put(bis_sk);
goto unlock;
}
+ lockdep_assert_held(&hcon->hdev->lock);
+
conn = iso_conn_add(hcon);
if (!conn) {
hci_conn_drop(hcon);
return -ENOENT;
}
+ spin_lock(&hcon->proto_lock);
conn = iso_conn_hold_unless_zero(hcon->iso_data);
+ spin_unlock(&hcon->proto_lock);
+
hcon = NULL;
hci_dev_unlock(hdev);