summaryrefslogtreecommitdiff
path: root/net/bluetooth/l2cap_sock.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/bluetooth/l2cap_sock.c')
-rw-r--r--net/bluetooth/l2cap_sock.c100
1 files changed, 74 insertions, 26 deletions
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 735167f73f31..1194c37e466f 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -436,11 +436,26 @@ static int l2cap_get_mode(struct l2cap_chan *chan)
return -EINVAL;
}
+static struct l2cap_conn *l2cap_chan_conn(struct l2cap_chan *chan)
+{
+ lockdep_assert_held(&chan->lock);
+
+ /* l2cap_conn_del() sets FLAG_DEL while holding chan->lock before
+ * conn->hcon is deleted. If not set and conn is non-NULL, conn->hcon
+ * remains alive during this chan->lock critical section.
+ */
+ if (test_bit(FLAG_DEL, &chan->flags))
+ return NULL;
+
+ return chan->conn;
+}
+
static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
sockopt_t *sopt)
{
struct sock *sk = sock->sk;
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+ struct l2cap_conn *conn;
struct l2cap_options opts;
struct l2cap_conninfo cinfo;
int err = 0;
@@ -451,6 +466,7 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
len = sopt->optlen;
+ l2cap_chan_lock(chan);
lock_sock(sk);
switch (optname) {
@@ -537,9 +553,15 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
break;
}
+ conn = l2cap_chan_conn(chan);
+ if (!conn) {
+ err = -ENOTCONN;
+ break;
+ }
+
memset(&cinfo, 0, sizeof(cinfo));
- cinfo.hci_handle = chan->conn->hcon->handle;
- memcpy(cinfo.dev_class, chan->conn->hcon->dev_class, 3);
+ cinfo.hci_handle = conn->hcon->handle;
+ memcpy(cinfo.dev_class, conn->hcon->dev_class, 3);
len = min(len, sizeof(cinfo));
if (copy_to_iter(&cinfo, len, &sopt->iter_out) != len)
@@ -553,6 +575,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
}
release_sock(sk);
+ l2cap_chan_unlock(chan);
+
return err;
}
@@ -561,6 +585,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
{
struct sock *sk = sock->sk;
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+ struct l2cap_conn *conn;
struct bt_security sec;
struct bt_power pwr;
int len, mode, err = 0;
@@ -578,6 +603,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
len = sopt->optlen;
+ l2cap_chan_lock(chan);
lock_sock(sk);
switch (optname) {
@@ -589,12 +615,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
break;
}
+ conn = l2cap_chan_conn(chan);
+
memset(&sec, 0, sizeof(sec));
- if (chan->conn) {
- sec.level = chan->conn->hcon->sec_level;
+ if (conn) {
+ sec.level = conn->hcon->sec_level;
if (sk->sk_state == BT_CONNECTED)
- sec.key_size = chan->conn->hcon->enc_key_size;
+ sec.key_size = conn->hcon->enc_key_size;
} else {
sec.level = chan->sec_level;
}
@@ -678,12 +706,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
break;
case BT_PHY:
- if (sk->sk_state != BT_CONNECTED) {
+ conn = l2cap_chan_conn(chan);
+
+ if (sk->sk_state != BT_CONNECTED || !conn) {
err = -ENOTCONN;
break;
}
- opt = hci_conn_get_phy(chan->conn->hcon);
+ opt = hci_conn_get_phy(conn->hcon);
if (copy_to_iter(&opt, sizeof(opt), &sopt->iter_out) !=
sizeof(opt))
@@ -719,6 +749,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
}
release_sock(sk);
+ l2cap_chan_unlock(chan);
return err;
}
@@ -749,6 +780,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
BT_DBG("sk %p", sk);
+ l2cap_chan_lock(chan);
lock_sock(sk);
switch (optname) {
@@ -850,6 +882,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
}
release_sock(sk);
+ l2cap_chan_unlock(chan);
return err;
}
@@ -913,6 +946,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
if (level != SOL_BLUETOOTH)
return -ENOPROTOOPT;
+ l2cap_chan_lock(chan);
lock_sock(sk);
switch (optname) {
@@ -938,11 +972,10 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
chan->sec_level = sec.level;
- if (!chan->conn)
+ conn = l2cap_chan_conn(chan);
+ if (!conn)
break;
- conn = chan->conn;
-
/* change security for LE channels */
if (chan->scid == L2CAP_CID_ATT) {
if (smp_conn_security(conn->hcon, sec.level)) {
@@ -997,7 +1030,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
}
if (opt == BT_FLUSHABLE_OFF) {
- conn = chan->conn;
+ conn = l2cap_chan_conn(chan);
+
/* proceed further only when we have l2cap_conn and
No Flush support in the LM */
if (!conn || !lmp_no_flush_capable(conn->hcon->hdev)) {
@@ -1083,7 +1117,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
break;
case BT_PHY:
- if (sk->sk_state != BT_CONNECTED) {
+ conn = l2cap_chan_conn(chan);
+ if (sk->sk_state != BT_CONNECTED || !conn) {
err = -ENOTCONN;
break;
}
@@ -1093,10 +1128,6 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
if (err)
break;
- if (!chan->conn)
- break;
-
- conn = chan->conn;
err = hci_conn_set_phy(conn->hcon, phys);
break;
@@ -1139,6 +1170,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
}
release_sock(sk);
+ l2cap_chan_unlock(chan);
return err;
}
@@ -1312,7 +1344,12 @@ static void l2cap_sock_kill(struct sock *sk)
BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
+ /* Take lock to synchronize against access without owning sk->sk_socket,
+ * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
+ */
+ lock_sock(sk);
l2cap_sock_put_chan(sk);
+ release_sock(sk);
/* Kill poor orphan */
sock_set_flag(sk, SOCK_DEAD);
@@ -1516,14 +1553,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
* establish sk_lock -> conn->lock and invert the established
* conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
*
- * Instead, briefly take the child sk lock to fetch and pin its chan.
- * l2cap_conn_del() reaches the chan free only via
- * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
- * the child sk lock; holding it across l2cap_chan_hold_unless_zero()
- * therefore guarantees the chan cannot be freed while we read and
- * pin it (hold_unless_zero() additionally skips a chan already past
- * its last reference). We then drop the sk lock before taking
- * chan->lock, so sk and chan locks are never held together.
+ * Instead, briefly take the child sk lock to synchronize vs.
+ * l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk
+ * lock before taking chan->lock, so sk and chan locks are never held
+ * together.
*
* Since we cannot call l2cap_chan_close() without conn->lock,
* schedule l2cap_chan_timeout to close the channel; it already
@@ -1533,10 +1566,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
struct l2cap_chan *chan;
lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
- chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
+ chan = l2cap_pi(sk)->chan;
+ if (chan)
+ l2cap_chan_hold(chan);
release_sock(sk);
if (!chan) {
- /* l2cap_conn_del() already tearing this child down */
+ /* Already torn down */
sock_put(sk);
continue;
}
@@ -1568,6 +1603,11 @@ static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
lock_sock(parent);
+ if (parent->sk_state != BT_LISTEN) {
+ release_sock(parent);
+ return -EINVAL;
+ }
+
/* Check for backlog size */
if (sk_acceptq_is_full(parent)) {
BT_DBG("backlog full %d", parent->sk_ack_backlog);
@@ -1731,10 +1771,14 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state,
if (!sk)
return;
+ lock_sock(sk);
+
sk->sk_state = state;
if (err)
sk->sk_err = err;
+
+ release_sock(sk);
}
static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan,
@@ -1810,6 +1854,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan)
if (!sk)
return;
+ lock_sock(sk);
+
if (test_and_clear_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
sk->sk_state = BT_CONNECTED;
chan->state = BT_CONNECTED;
@@ -1817,6 +1863,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan)
clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
sk->sk_state_change(sk);
+
+ release_sock(sk);
}
static void l2cap_sock_set_shutdown_cb(struct l2cap_chan *chan)