mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
A NULL pointer dereference in klist_put() occurs when a child device (such as a BNEP network device in bnep_session) is concurrently being unregistered while hci_conn_del_sysfs() reparents child devices. This is caused by a race condition between hci_conn_del_sysfs() and concurrent child device unregistration (e.g. bnep_session calling unregister_netdev()). During device unregistration, device_del() snapshots a non-NULL parent pointer. Concurrently, hci_conn_del_sysfs() finds the child device using device_find_any_child() and calls device_move() to reparent it to NULL, which removes the node from its parent's klist and clears knode_parent. Subsequently, device_del() calls klist_del(&dev->p->knode_parent) using the stale parent snapshot, causing klist_put() to dereference knode_klist(n)->put on an already removed node, resulting in a NULL pointer dereference. This race was introduced by commit27aabf27fd("Bluetooth: fix use-after-free in device_for_each_child()"), which replaced device_find_child(..., __match_tty) with device_find_any_child() in hci_conn_del_sysfs(). That change was intended to avoid a use-after-free where conn->dev outlived its parent hdev->dev when child devices held references to conn->dev, because conn->dev only held a reference to hdev->dev while registered in sysfs. Fix the issue properly by taking an explicit reference to the parent device with get_device(&hdev->dev) in hci_conn_init_sysfs() and dropping it with put_device(parent) in bt_link_release() when the conn device is freed. This ensures that hdev->dev remains valid for the entire lifecycle of conn->dev, resolving the underlying use-after-free. With the parent reference held properly, restore the __match_tty filter in hci_conn_del_sysfs() so that device_move() is only invoked on persistent RFCOMM TTY devices as originally intended, eliminating the race condition with unregistering network devices. Fixes:27aabf27fd("Bluetooth: fix use-after-free in device_for_each_child()") Assisted-by: Gemini:gemini-3.7-flash syzbot Reported-by: syzbot+6df45dd3d03e1a9aca96@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6df45dd3d03e1a9aca96 Link: https://syzkaller.appspot.com/ai_job?id=f1c0e740-db21-40af-a9ff-84db0fd8b8bd Signed-off-by: Krystian Kaniewski <krystianmkaniewski@gmail.com> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
152 lines
3.0 KiB
C
152 lines
3.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Bluetooth HCI driver model support. */
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <net/bluetooth/bluetooth.h>
|
|
#include <net/bluetooth/hci_core.h>
|
|
|
|
static const struct class bt_class = {
|
|
.name = "bluetooth",
|
|
};
|
|
|
|
static void bt_link_release(struct device *dev)
|
|
{
|
|
struct hci_conn *conn = to_hci_conn(dev);
|
|
struct device *parent = dev->parent;
|
|
|
|
kfree(conn);
|
|
put_device(parent);
|
|
}
|
|
|
|
static const struct device_type bt_link = {
|
|
.name = "link",
|
|
.release = bt_link_release,
|
|
};
|
|
|
|
/*
|
|
* The rfcomm tty device will possibly retain even when conn
|
|
* is down, and sysfs doesn't support move zombie device,
|
|
* so we should move the device before conn device is destroyed.
|
|
*/
|
|
static int __match_tty(struct device *dev, const void *data)
|
|
{
|
|
return !strncmp(dev_name(dev), "rfcomm", 6);
|
|
}
|
|
|
|
void hci_conn_init_sysfs(struct hci_conn *conn)
|
|
{
|
|
struct hci_dev *hdev = conn->hdev;
|
|
|
|
bt_dev_dbg(hdev, "conn %p", conn);
|
|
|
|
conn->dev.type = &bt_link;
|
|
conn->dev.class = &bt_class;
|
|
conn->dev.parent = get_device(&hdev->dev);
|
|
|
|
device_initialize(&conn->dev);
|
|
}
|
|
|
|
void hci_conn_add_sysfs(struct hci_conn *conn)
|
|
{
|
|
struct hci_dev *hdev = conn->hdev;
|
|
|
|
bt_dev_dbg(hdev, "conn %p", conn);
|
|
|
|
if (device_is_registered(&conn->dev))
|
|
return;
|
|
|
|
dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle);
|
|
|
|
if (device_add(&conn->dev) < 0)
|
|
bt_dev_err(hdev, "failed to register connection device");
|
|
}
|
|
|
|
void hci_conn_del_sysfs(struct hci_conn *conn)
|
|
{
|
|
struct hci_dev *hdev = conn->hdev;
|
|
|
|
bt_dev_dbg(hdev, "conn %p", conn);
|
|
|
|
if (!device_is_registered(&conn->dev)) {
|
|
/* If device_add() has *not* succeeded, use *only* put_device()
|
|
* to drop the reference count.
|
|
*/
|
|
put_device(&conn->dev);
|
|
return;
|
|
}
|
|
|
|
/* If there are devices using the connection as parent reset it to NULL
|
|
* before unregistering the device.
|
|
*/
|
|
while (1) {
|
|
struct device *dev;
|
|
|
|
dev = device_find_child(&conn->dev, NULL, __match_tty);
|
|
if (!dev)
|
|
break;
|
|
device_move(dev, NULL, DPM_ORDER_DEV_LAST);
|
|
put_device(dev);
|
|
}
|
|
|
|
device_unregister(&conn->dev);
|
|
}
|
|
|
|
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_release_dev(hdev);
|
|
} else {
|
|
cleanup_srcu_struct(&hdev->srcu);
|
|
kfree(hdev);
|
|
}
|
|
module_put(THIS_MODULE);
|
|
}
|
|
|
|
static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct hci_dev *hdev = to_hci_dev(dev);
|
|
|
|
if (hdev->reset)
|
|
hdev->reset(hdev);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(reset);
|
|
|
|
static struct attribute *bt_host_attrs[] = {
|
|
&dev_attr_reset.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(bt_host);
|
|
|
|
static const struct device_type bt_host = {
|
|
.name = "host",
|
|
.release = bt_host_release,
|
|
.groups = bt_host_groups,
|
|
};
|
|
|
|
void hci_init_sysfs(struct hci_dev *hdev)
|
|
{
|
|
struct device *dev = &hdev->dev;
|
|
|
|
dev->type = &bt_host;
|
|
dev->class = &bt_class;
|
|
|
|
__module_get(THIS_MODULE);
|
|
device_initialize(dev);
|
|
}
|
|
|
|
int __init bt_sysfs_init(void)
|
|
{
|
|
return class_register(&bt_class);
|
|
}
|
|
|
|
void bt_sysfs_cleanup(void)
|
|
{
|
|
class_unregister(&bt_class);
|
|
}
|