Bluetooth: Lock sockets when closing HCI device
When a Bluetooth device is powered down while RFCOMM connections are
open, there are race conditions where RFCOMM tries to use an HCI
connection after the data structures have been deleted. Since the
powerdown procedure runs in process context, it is possible to lock
sockets before accessing them to close connections. This change adds
that locking, which will serialize socket access between processors
during powerdown.
Change-Id: I587720799b35d9ee6e66340bdeda8a2b9e2ea7b5
CRs-fixed: 336893
Signed-off-by: Mat Martineau <mathewm@codeaurora.org>
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 899c538..ca33664 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -728,7 +728,7 @@
{
BT_DBG("conn %p", conn);
- hci_proto_disconn_cfm(conn, reason);
+ hci_proto_disconn_cfm(conn, reason, 0);
}
EXPORT_SYMBOL(hci_disconnect);
@@ -1027,7 +1027,7 @@
EXPORT_SYMBOL(hci_chan_modify);
/* Drop all connection on the device */
-void hci_conn_hash_flush(struct hci_dev *hdev)
+void hci_conn_hash_flush(struct hci_dev *hdev, u8 is_process)
{
struct hci_conn_hash *h = &hdev->conn_hash;
struct list_head *p;
@@ -1043,7 +1043,7 @@
c->state = BT_CLOSED;
- hci_proto_disconn_cfm(c, 0x16);
+ hci_proto_disconn_cfm(c, 0x16, is_process);
hci_conn_del(c);
}
}
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 4c2bd37..c0eb50c 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -626,7 +626,7 @@
return ret;
}
-static int hci_dev_do_close(struct hci_dev *hdev)
+static int hci_dev_do_close(struct hci_dev *hdev, u8 is_process)
{
unsigned long keepflags = 0;
@@ -647,7 +647,7 @@
hci_dev_lock_bh(hdev);
inquiry_cache_flush(hdev);
- hci_conn_hash_flush(hdev);
+ hci_conn_hash_flush(hdev, is_process);
hci_dev_unlock_bh(hdev);
hci_notify(hdev, HCI_DEV_DOWN);
@@ -714,7 +714,7 @@
hdev = hci_dev_get(dev);
if (!hdev)
return -ENODEV;
- err = hci_dev_do_close(hdev);
+ err = hci_dev_do_close(hdev, 1);
hci_dev_put(hdev);
return err;
}
@@ -740,7 +740,7 @@
hci_dev_lock_bh(hdev);
inquiry_cache_flush(hdev);
- hci_conn_hash_flush(hdev);
+ hci_conn_hash_flush(hdev, 0);
hci_dev_unlock_bh(hdev);
if (hdev->flush)
@@ -953,7 +953,7 @@
if (!blocked)
return 0;
- hci_dev_do_close(hdev);
+ hci_dev_do_close(hdev, 0);
return 0;
}
@@ -1563,7 +1563,7 @@
list_del(&hdev->list);
write_unlock_bh(&hci_dev_list_lock);
- hci_dev_do_close(hdev);
+ hci_dev_do_close(hdev, 0);
for (i = 0; i < NUM_REASSEMBLY; i++)
kfree_skb(hdev->reassembly[i]);
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 86479ab..a1a5a72 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -1764,7 +1764,7 @@
if (conn->type == LE_LINK)
del_timer(&conn->smp_timer);
- hci_proto_disconn_cfm(conn, ev->reason);
+ hci_proto_disconn_cfm(conn, ev->reason, 0);
hci_conn_del(conn);
unlock:
@@ -3305,7 +3305,7 @@
if (conn) {
conn->state = BT_CLOSED;
- hci_proto_disconn_cfm(conn, ev->reason);
+ hci_proto_disconn_cfm(conn, ev->reason, 0);
hci_conn_del(conn);
}
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index a3d5e34..6a95c79 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -89,7 +89,7 @@
static int l2cap_create_cfm(struct hci_chan *chan, u8 status);
static int l2cap_deaggregate(struct hci_chan *chan, struct l2cap_pinfo *pi);
static void l2cap_chan_ready(struct sock *sk);
-static void l2cap_conn_del(struct hci_conn *hcon, int err);
+static void l2cap_conn_del(struct hci_conn *hcon, int err, u8 is_process);
static u16 l2cap_get_smallest_flushto(struct l2cap_chan_list *l);
static void l2cap_set_acl_flushto(struct hci_conn *hcon, u16 flush_to);
@@ -1160,7 +1160,7 @@
return conn;
}
-static void l2cap_conn_del(struct hci_conn *hcon, int err)
+static void l2cap_conn_del(struct hci_conn *hcon, int err, u8 is_process)
{
struct l2cap_conn *conn = hcon->l2cap_data;
struct sock *sk;
@@ -1181,9 +1181,15 @@
BT_DBG("ampcon %p", l2cap_pi(sk)->ampcon);
if ((conn->hcon == hcon) || (l2cap_pi(sk)->ampcon == hcon)) {
next = l2cap_pi(sk)->next_c;
- bh_lock_sock(sk);
+ if (is_process)
+ lock_sock(sk);
+ else
+ bh_lock_sock(sk);
l2cap_chan_del(sk, err);
- bh_unlock_sock(sk);
+ if (is_process)
+ release_sock(sk);
+ else
+ bh_unlock_sock(sk);
l2cap_sock_kill(sk);
sk = next;
} else
@@ -7332,7 +7338,7 @@
case L2CAP_CID_SMP:
if (smp_sig_channel(conn, skb))
- l2cap_conn_del(conn->hcon, EACCES);
+ l2cap_conn_del(conn->hcon, EACCES, 0);
break;
default:
@@ -7407,7 +7413,7 @@
if (conn)
l2cap_conn_ready(conn);
} else
- l2cap_conn_del(hcon, bt_err(status));
+ l2cap_conn_del(hcon, bt_err(status), 0);
return 0;
}
@@ -7424,14 +7430,14 @@
return conn->disc_reason;
}
-static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
+static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason, u8 is_process)
{
BT_DBG("hcon %p reason %d", hcon, reason);
if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK))
return -EINVAL;
- l2cap_conn_del(hcon, bt_err(reason));
+ l2cap_conn_del(hcon, bt_err(reason), is_process);
return 0;
}
diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index 99c4559..f8c3bba 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -62,7 +62,7 @@
static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent);
static void sco_chan_del(struct sock *sk, int err);
-static int sco_conn_del(struct hci_conn *conn, int err);
+static int sco_conn_del(struct hci_conn *conn, int err, u8 is_process);
static void sco_sock_close(struct sock *sk);
static void sco_sock_kill(struct sock *sk);
@@ -135,7 +135,7 @@
return sk;
}
-static int sco_conn_del(struct hci_conn *hcon, int err)
+static int sco_conn_del(struct hci_conn *hcon, int err, u8 is_process)
{
struct sco_conn *conn = hcon->sco_data;
struct sock *sk;
@@ -148,10 +148,16 @@
/* Kill socket */
sk = sco_chan_get(conn);
if (sk) {
- bh_lock_sock(sk);
+ if (is_process)
+ lock_sock(sk);
+ else
+ bh_lock_sock(sk);
sco_sock_clear_timer(sk);
sco_chan_del(sk, err);
- bh_unlock_sock(sk);
+ if (is_process)
+ release_sock(sk);
+ else
+ bh_unlock_sock(sk);
sco_sock_kill(sk);
}
@@ -952,19 +958,19 @@
if (conn)
sco_conn_ready(conn);
} else
- sco_conn_del(hcon, bt_err(status));
+ sco_conn_del(hcon, bt_err(status), 0);
return 0;
}
-static int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason)
+static int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason, __u8 is_process)
{
BT_DBG("hcon %p reason %d", hcon, reason);
if (hcon->type != SCO_LINK && hcon->type != ESCO_LINK)
return -EINVAL;
- sco_conn_del(hcon, bt_err(reason));
+ sco_conn_del(hcon, bt_err(reason), is_process);
return 0;
}