Bluetooth: defer cleanup of resources in hci_unregister_dev()
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. Commitb40df5743e
("[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, commit4ce61d1c7a
("[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, commit4b5dd696f8
("Bluetooth: Remove local_bh_disable() from hci_sock.c") in 3.3-rc1 removed local_bh_disable(). Then, commite305509e67
("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 <syzbot+a5df189917e79d5e59c9@syzkaller.appspotmail.com> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> Fixes:e305509e67
("Bluetooth: use correct lock to prevent UAF of hdev object") Acked-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Родитель
0b53abfc5f
Коммит
e04480920d
|
@ -1230,6 +1230,7 @@ struct hci_dev *hci_alloc_dev(void);
|
||||||
void hci_free_dev(struct hci_dev *hdev);
|
void hci_free_dev(struct hci_dev *hdev);
|
||||||
int hci_register_dev(struct hci_dev *hdev);
|
int hci_register_dev(struct hci_dev *hdev);
|
||||||
void hci_unregister_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_suspend_dev(struct hci_dev *hdev);
|
||||||
int hci_resume_dev(struct hci_dev *hdev);
|
int hci_resume_dev(struct hci_dev *hdev);
|
||||||
int hci_reset_dev(struct hci_dev *hdev);
|
int hci_reset_dev(struct hci_dev *hdev);
|
||||||
|
|
|
@ -3996,14 +3996,10 @@ EXPORT_SYMBOL(hci_register_dev);
|
||||||
/* Unregister HCI device */
|
/* Unregister HCI device */
|
||||||
void hci_unregister_dev(struct hci_dev *hdev)
|
void hci_unregister_dev(struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
int id;
|
|
||||||
|
|
||||||
BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus);
|
BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus);
|
||||||
|
|
||||||
hci_dev_set_flag(hdev, HCI_UNREGISTER);
|
hci_dev_set_flag(hdev, HCI_UNREGISTER);
|
||||||
|
|
||||||
id = hdev->id;
|
|
||||||
|
|
||||||
write_lock(&hci_dev_list_lock);
|
write_lock(&hci_dev_list_lock);
|
||||||
list_del(&hdev->list);
|
list_del(&hdev->list);
|
||||||
write_unlock(&hci_dev_list_lock);
|
write_unlock(&hci_dev_list_lock);
|
||||||
|
@ -4038,7 +4034,14 @@ void hci_unregister_dev(struct hci_dev *hdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
device_del(&hdev->dev);
|
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);
|
debugfs_remove_recursive(hdev->debugfs);
|
||||||
kfree_const(hdev->hw_info);
|
kfree_const(hdev->hw_info);
|
||||||
kfree_const(hdev->fw_info);
|
kfree_const(hdev->fw_info);
|
||||||
|
@ -4063,11 +4066,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
|
||||||
hci_blocked_keys_clear(hdev);
|
hci_blocked_keys_clear(hdev);
|
||||||
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
||||||
|
|
||||||
hci_dev_put(hdev);
|
ida_simple_remove(&hci_index_ida, hdev->id);
|
||||||
|
|
||||||
ida_simple_remove(&hci_index_ida, id);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(hci_unregister_dev);
|
|
||||||
|
|
||||||
/* Suspend HCI device */
|
/* Suspend HCI device */
|
||||||
int hci_suspend_dev(struct hci_dev *hdev)
|
int hci_suspend_dev(struct hci_dev *hdev)
|
||||||
|
|
|
@ -59,6 +59,17 @@ struct hci_pinfo {
|
||||||
char comm[TASK_COMM_LEN];
|
char comm[TASK_COMM_LEN];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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)
|
void hci_sock_set_flag(struct sock *sk, int nr)
|
||||||
{
|
{
|
||||||
set_bit(nr, &hci_pi(sk)->flags);
|
set_bit(nr, &hci_pi(sk)->flags);
|
||||||
|
@ -759,19 +770,13 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event)
|
||||||
if (event == HCI_DEV_UNREG) {
|
if (event == HCI_DEV_UNREG) {
|
||||||
struct sock *sk;
|
struct sock *sk;
|
||||||
|
|
||||||
/* Detach sockets from device */
|
/* Wake up sockets using this dead device */
|
||||||
read_lock(&hci_sk_list.lock);
|
read_lock(&hci_sk_list.lock);
|
||||||
sk_for_each(sk, &hci_sk_list.head) {
|
sk_for_each(sk, &hci_sk_list.head) {
|
||||||
lock_sock(sk);
|
|
||||||
if (hci_pi(sk)->hdev == hdev) {
|
if (hci_pi(sk)->hdev == hdev) {
|
||||||
hci_pi(sk)->hdev = NULL;
|
|
||||||
sk->sk_err = EPIPE;
|
sk->sk_err = EPIPE;
|
||||||
sk->sk_state = BT_OPEN;
|
|
||||||
sk->sk_state_change(sk);
|
sk->sk_state_change(sk);
|
||||||
|
|
||||||
hci_dev_put(hdev);
|
|
||||||
}
|
}
|
||||||
release_sock(sk);
|
|
||||||
}
|
}
|
||||||
read_unlock(&hci_sk_list.lock);
|
read_unlock(&hci_sk_list.lock);
|
||||||
}
|
}
|
||||||
|
@ -930,10 +935,10 @@ static int hci_sock_reject_list_del(struct hci_dev *hdev, void __user *arg)
|
||||||
static int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd,
|
static int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd,
|
||||||
unsigned long arg)
|
unsigned long arg)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = hci_pi(sk)->hdev;
|
struct hci_dev *hdev = hci_hdev_from_sock(sk);
|
||||||
|
|
||||||
if (!hdev)
|
if (IS_ERR(hdev))
|
||||||
return -EBADFD;
|
return PTR_ERR(hdev);
|
||||||
|
|
||||||
if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
|
if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
@ -1103,6 +1108,18 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
|
||||||
|
|
||||||
lock_sock(sk);
|
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) {
|
if (sk->sk_state == BT_BOUND) {
|
||||||
err = -EALREADY;
|
err = -EALREADY;
|
||||||
goto done;
|
goto done;
|
||||||
|
@ -1379,9 +1396,9 @@ static int hci_sock_getname(struct socket *sock, struct sockaddr *addr,
|
||||||
|
|
||||||
lock_sock(sk);
|
lock_sock(sk);
|
||||||
|
|
||||||
hdev = hci_pi(sk)->hdev;
|
hdev = hci_hdev_from_sock(sk);
|
||||||
if (!hdev) {
|
if (IS_ERR(hdev)) {
|
||||||
err = -EBADFD;
|
err = PTR_ERR(hdev);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1743,9 +1760,9 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
hdev = hci_pi(sk)->hdev;
|
hdev = hci_hdev_from_sock(sk);
|
||||||
if (!hdev) {
|
if (IS_ERR(hdev)) {
|
||||||
err = -EBADFD;
|
err = PTR_ERR(hdev);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,9 @@ void hci_conn_del_sysfs(struct hci_conn *conn)
|
||||||
static void bt_host_release(struct device *dev)
|
static void bt_host_release(struct device *dev)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = to_hci_dev(dev);
|
struct hci_dev *hdev = to_hci_dev(dev);
|
||||||
|
|
||||||
|
if (hci_dev_test_flag(hdev, HCI_UNREGISTER))
|
||||||
|
hci_cleanup_dev(hdev);
|
||||||
kfree(hdev);
|
kfree(hdev);
|
||||||
module_put(THIS_MODULE);
|
module_put(THIS_MODULE);
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче