mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 18:09:56 +00:00
Bluetooth: 6lowpan: Don't hold spin lock over sleeping functions
[ Upstream commit98454bc812] disconnect_all_peers() calls sleeping function (l2cap_chan_close) under spinlock. Holding the lock doesn't actually do any good -- we work on a local copy of the list, and the lock doesn't protect against peer->chan having already been freed. Fix by taking refcounts of peer->chan instead. Clean up the code and old comments a bit. Take devices_lock instead of RCU, because the kfree_rcu(); l2cap_chan_put(); construct in chan_close_cb() does not guarantee peer->chan is necessarily valid in RCU. Also take l2cap_chan_lock() which is required for l2cap_chan_close(). Log: (bluez 6lowpan-tester Client Connect - Disable) ------ BUG: sleeping function called from invalid context at kernel/locking/mutex.c:575 ... <TASK> ... l2cap_send_disconn_req (net/bluetooth/l2cap_core.c:938 net/bluetooth/l2cap_core.c:1495) ... ? __pfx_l2cap_chan_close (net/bluetooth/l2cap_core.c:809) do_enable_set (net/bluetooth/6lowpan.c:1048 net/bluetooth/6lowpan.c:1068) ------ Fixes:9030582963("Bluetooth: 6lowpan: Converting rwlocks to use RCU") Signed-off-by: Pauli Virtanen <pav@iki.fi> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
9feffe9e1a
commit
0eec6c8cbb
@@ -53,6 +53,11 @@ static bool enable_6lowpan;
|
|||||||
static struct l2cap_chan *listen_chan;
|
static struct l2cap_chan *listen_chan;
|
||||||
static DEFINE_MUTEX(set_lock);
|
static DEFINE_MUTEX(set_lock);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LOWPAN_PEER_CLOSING,
|
||||||
|
LOWPAN_PEER_MAXBITS
|
||||||
|
};
|
||||||
|
|
||||||
struct lowpan_peer {
|
struct lowpan_peer {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct rcu_head rcu;
|
struct rcu_head rcu;
|
||||||
@@ -61,6 +66,8 @@ struct lowpan_peer {
|
|||||||
/* peer addresses in various formats */
|
/* peer addresses in various formats */
|
||||||
unsigned char lladdr[ETH_ALEN];
|
unsigned char lladdr[ETH_ALEN];
|
||||||
struct in6_addr peer_addr;
|
struct in6_addr peer_addr;
|
||||||
|
|
||||||
|
DECLARE_BITMAP(flags, LOWPAN_PEER_MAXBITS);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lowpan_btle_dev {
|
struct lowpan_btle_dev {
|
||||||
@@ -1014,41 +1021,52 @@ static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type,
|
|||||||
static void disconnect_all_peers(void)
|
static void disconnect_all_peers(void)
|
||||||
{
|
{
|
||||||
struct lowpan_btle_dev *entry;
|
struct lowpan_btle_dev *entry;
|
||||||
struct lowpan_peer *peer, *tmp_peer, *new_peer;
|
struct lowpan_peer *peer;
|
||||||
struct list_head peers;
|
int nchans;
|
||||||
|
|
||||||
INIT_LIST_HEAD(&peers);
|
/* l2cap_chan_close() cannot be called from RCU, and lock ordering
|
||||||
|
* chan->lock > devices_lock prevents taking write side lock, so copy
|
||||||
/* We make a separate list of peers as the close_cb() will
|
* then close.
|
||||||
* modify the device peers list so it is better not to mess
|
|
||||||
* with the same list at the same time.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
|
list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list)
|
||||||
list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
|
list_for_each_entry_rcu(peer, &entry->peers, list)
|
||||||
list_for_each_entry_rcu(peer, &entry->peers, list) {
|
clear_bit(LOWPAN_PEER_CLOSING, peer->flags);
|
||||||
new_peer = kmalloc(sizeof(*new_peer), GFP_ATOMIC);
|
|
||||||
if (!new_peer)
|
|
||||||
break;
|
|
||||||
|
|
||||||
new_peer->chan = peer->chan;
|
|
||||||
INIT_LIST_HEAD(&new_peer->list);
|
|
||||||
|
|
||||||
list_add(&new_peer->list, &peers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
spin_lock(&devices_lock);
|
do {
|
||||||
list_for_each_entry_safe(peer, tmp_peer, &peers, list) {
|
struct l2cap_chan *chans[32];
|
||||||
l2cap_chan_close(peer->chan, ENOENT);
|
int i;
|
||||||
|
|
||||||
list_del_rcu(&peer->list);
|
nchans = 0;
|
||||||
kfree_rcu(peer, rcu);
|
|
||||||
}
|
spin_lock(&devices_lock);
|
||||||
spin_unlock(&devices_lock);
|
|
||||||
|
list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
|
||||||
|
list_for_each_entry_rcu(peer, &entry->peers, list) {
|
||||||
|
if (test_and_set_bit(LOWPAN_PEER_CLOSING,
|
||||||
|
peer->flags))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
l2cap_chan_hold(peer->chan);
|
||||||
|
chans[nchans++] = peer->chan;
|
||||||
|
|
||||||
|
if (nchans >= ARRAY_SIZE(chans))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
spin_unlock(&devices_lock);
|
||||||
|
|
||||||
|
for (i = 0; i < nchans; ++i) {
|
||||||
|
l2cap_chan_lock(chans[i]);
|
||||||
|
l2cap_chan_close(chans[i], ENOENT);
|
||||||
|
l2cap_chan_unlock(chans[i]);
|
||||||
|
l2cap_chan_put(chans[i]);
|
||||||
|
}
|
||||||
|
} while (nchans);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct set_enable {
|
struct set_enable {
|
||||||
|
|||||||
Reference in New Issue
Block a user