Skip to content

Commit

Permalink
Bluetooth: defer cleanup of resources in hci_unregister_dev()
Browse files Browse the repository at this point in the history
[ Upstream commit e04480920d1eec9c061841399aa6f35b6f987d8b ]

syzbot is hitting might_sleep() warning at hci_sock_dev_event() due to
calling lock_sock() with rw spinlock held [1].

It seems that history of this locking problem is a trial and error.

Commit b40df57 ("[PATCH] bluetooth: fix socket locking in
hci_sock_dev_event()") in 2.6.21-rc4 changed bh_lock_sock() to
lock_sock() as an attempt to fix lockdep warning.

Then, commit 4ce61d1 ("[BLUETOOTH]: Fix locking in
hci_sock_dev_event().") in 2.6.22-rc2 changed lock_sock() to
local_bh_disable() + bh_lock_sock_nested() as an attempt to fix the
sleep in atomic context warning.

Then, commit 4b5dd69 ("Bluetooth: Remove local_bh_disable() from
hci_sock.c") in 3.3-rc1 removed local_bh_disable().

Then, commit e305509e678b ("Bluetooth: use correct lock to prevent UAF
of hdev object") in 5.13-rc5 again changed bh_lock_sock_nested() to
lock_sock() as an attempt to fix CVE-2021-3573.

This difficulty comes from current implementation that
hci_sock_dev_event(HCI_DEV_UNREG) is responsible for dropping all
references from sockets because hci_unregister_dev() immediately
reclaims resources as soon as returning from
hci_sock_dev_event(HCI_DEV_UNREG).

But the history suggests that hci_sock_dev_event(HCI_DEV_UNREG) was not
doing what it should do.

Therefore, instead of trying to detach sockets from device, let's accept
not detaching sockets from device at hci_sock_dev_event(HCI_DEV_UNREG),
by moving actual cleanup of resources from hci_unregister_dev() to
hci_cleanup_dev() which is called by bt_host_release() when all
references to this unregistered device (which is a kobject) are gone.

Since hci_sock_dev_event(HCI_DEV_UNREG) no longer resets
hci_pi(sk)->hdev, we need to check whether this device was unregistered
and return an error based on HCI_UNREGISTER flag.  There might be subtle
behavioral difference in "monitor the hdev" functionality; please report
if you found something went wrong due to this patch.

Link: https://syzkaller.appspot.com/bug?extid=a5df189917e79d5e59c9 [1]
Reported-by: syzbot <[email protected]>
Suggested-by: Linus Torvalds <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
Fixes: e305509e678b ("Bluetooth: use correct lock to prevent UAF of hdev object")
Acked-by: Luiz Augusto von Dentz <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
  • Loading branch information
Tetsuo Handa authored and gregkh committed Aug 15, 2021
1 parent 914054d commit d6ef8bb
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 24 deletions.
1 change: 1 addition & 0 deletions include/net/bluetooth/hci_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@ struct hci_dev *hci_alloc_dev(void);
void hci_free_dev(struct hci_dev *hdev);
int hci_register_dev(struct hci_dev *hdev);
void hci_unregister_dev(struct hci_dev *hdev);
void hci_cleanup_dev(struct hci_dev *hdev);
int hci_suspend_dev(struct hci_dev *hdev);
int hci_resume_dev(struct hci_dev *hdev);
int hci_reset_dev(struct hci_dev *hdev);
Expand Down
16 changes: 8 additions & 8 deletions net/bluetooth/hci_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -3457,14 +3457,10 @@ EXPORT_SYMBOL(hci_register_dev);
/* Unregister HCI device */
void hci_unregister_dev(struct hci_dev *hdev)
{
int id;

BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus);

hci_dev_set_flag(hdev, HCI_UNREGISTER);

id = hdev->id;

write_lock(&hci_dev_list_lock);
list_del(&hdev->list);
write_unlock(&hci_dev_list_lock);
Expand Down Expand Up @@ -3493,7 +3489,14 @@ void hci_unregister_dev(struct hci_dev *hdev)
}

device_del(&hdev->dev);
/* Actual cleanup is deferred until hci_cleanup_dev(). */
hci_dev_put(hdev);
}
EXPORT_SYMBOL(hci_unregister_dev);

/* Cleanup HCI device */
void hci_cleanup_dev(struct hci_dev *hdev)
{
debugfs_remove_recursive(hdev->debugfs);

destroy_workqueue(hdev->workqueue);
Expand All @@ -3513,11 +3516,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
hci_discovery_filter_clear(hdev);
hci_dev_unlock(hdev);

hci_dev_put(hdev);

ida_simple_remove(&hci_index_ida, id);
ida_simple_remove(&hci_index_ida, hdev->id);
}
EXPORT_SYMBOL(hci_unregister_dev);

/* Suspend HCI device */
int hci_suspend_dev(struct hci_dev *hdev)
Expand Down
49 changes: 33 additions & 16 deletions net/bluetooth/hci_sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ struct hci_pinfo {
unsigned long flags;
};

static struct hci_dev *hci_hdev_from_sock(struct sock *sk)
{
struct hci_dev *hdev = hci_pi(sk)->hdev;

if (!hdev)
return ERR_PTR(-EBADFD);
if (hci_dev_test_flag(hdev, HCI_UNREGISTER))
return ERR_PTR(-EPIPE);
return hdev;
}

void hci_sock_set_flag(struct sock *sk, int nr)
{
set_bit(nr, &hci_pi(sk)->flags);
Expand Down Expand Up @@ -480,19 +491,13 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event)
if (event == HCI_DEV_UNREG) {
struct sock *sk;

/* Detach sockets from device */
/* Wake up sockets using this dead device */
read_lock(&hci_sk_list.lock);
sk_for_each(sk, &hci_sk_list.head) {
lock_sock(sk);
if (hci_pi(sk)->hdev == hdev) {
hci_pi(sk)->hdev = NULL;
sk->sk_err = EPIPE;
sk->sk_state = BT_OPEN;
sk->sk_state_change(sk);

hci_dev_put(hdev);
}
release_sock(sk);
}
read_unlock(&hci_sk_list.lock);
}
Expand Down Expand Up @@ -631,10 +636,10 @@ static int hci_sock_blacklist_del(struct hci_dev *hdev, void __user *arg)
static int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd,
unsigned long arg)
{
struct hci_dev *hdev = hci_pi(sk)->hdev;
struct hci_dev *hdev = hci_hdev_from_sock(sk);

if (!hdev)
return -EBADFD;
if (IS_ERR(hdev))
return PTR_ERR(hdev);

if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
return -EBUSY;
Expand Down Expand Up @@ -766,6 +771,18 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,

lock_sock(sk);

/* Allow detaching from dead device and attaching to alive device, if
* the caller wants to re-bind (instead of close) this socket in
* response to hci_sock_dev_event(HCI_DEV_UNREG) notification.
*/
hdev = hci_pi(sk)->hdev;
if (hdev && hci_dev_test_flag(hdev, HCI_UNREGISTER)) {
hci_pi(sk)->hdev = NULL;
sk->sk_state = BT_OPEN;
hci_dev_put(hdev);
}
hdev = NULL;

if (sk->sk_state == BT_BOUND) {
err = -EALREADY;
goto done;
Expand Down Expand Up @@ -937,9 +954,9 @@ static int hci_sock_getname(struct socket *sock, struct sockaddr *addr,

lock_sock(sk);

hdev = hci_pi(sk)->hdev;
if (!hdev) {
err = -EBADFD;
hdev = hci_hdev_from_sock(sk);
if (IS_ERR(hdev)) {
err = PTR_ERR(hdev);
goto done;
}

Expand Down Expand Up @@ -1191,9 +1208,9 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
goto done;
}

hdev = hci_pi(sk)->hdev;
if (!hdev) {
err = -EBADFD;
hdev = hci_hdev_from_sock(sk);
if (IS_ERR(hdev)) {
err = PTR_ERR(hdev);
goto done;
}

Expand Down
3 changes: 3 additions & 0 deletions net/bluetooth/hci_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ ATTRIBUTE_GROUPS(bt_host);
static void bt_host_release(struct device *dev)
{
struct hci_dev *hdev = to_hci_dev(dev);

if (hci_dev_test_flag(hdev, HCI_UNREGISTER))
hci_cleanup_dev(hdev);
kfree(hdev);
module_put(THIS_MODULE);
}
Expand Down

0 comments on commit d6ef8bb

Please sign in to comment.