Technical Root Cause Analysis
1. Crash Site and Manifestation
The reported failure is a kernel warning triggered in the workqueue infrastructure:
- Location:
kernel/workqueue.c:4570 in __flush_work(), called via:
disable_delayed_work_sync(&conn->disc_work) $\to$ __cancel_work_sync(&dwork->work, ...) $\to$ __flush_work()
- Triggering Check:
if (WARN_ON(!work->func))
return false;
- Context:
The call stack originates during virtual HCI device release (
vhci_release in drivers/bluetooth/hci_vhci.c), which unregisters the device (hci_unregister_dev) and executes hci_dev_close_sync() in net/bluetooth/hci_sync.c.
Inside hci_dev_close_sync() (line 5681), the device connection table is flushed via hci_conn_hash_flush(hdev):
while ((conn = list_first_entry_or_null(head, struct hci_conn, list)) != NULL) {
conn->state = BT_CLOSED;
hci_disconn_cfm(conn, HCI_ERROR_LOCAL_HOST_TERM);
hci_conn_del(conn);
}
hci_conn_del() immediately attempts to cancel pending delayed work items:
hci_conn_unlink(conn);
disable_delayed_work_sync(&conn->disc_work);
disable_delayed_work_sync(&conn->auto_accept_work);
disable_delayed_work_sync(&conn->idle_work);
2. Register and Memory Layout Analysis
From the register state in the crash dump:
x20 = ffff0000c89b4a50: this is the argument work passed to __flush_work(), corresponding to &conn->disc_work.work.
- In
struct hci_conn:
disc_work is located at byte offset 2640 (0xa50).
- The base address of
conn is 0xffff0000c89b4000 (an 8KB-aligned slab object).
x26 = ffff0000c89b4a68: this is the address of work->func (offset 0x18 = 24 bytes within struct work_struct, byte offset 2664 = 0xa68 in struct hci_conn).
x0 = 0x0000000000000000: the value loaded from work->func was NULL.
3. Invariants and Verified Object Lifecycle
- Initialization: Every
struct hci_conn is allocated via kzalloc_obj(*conn) in __hci_conn_add() (net/bluetooth/hci_conn.c:1060) and its delayed work items are explicitly initialized:
INIT_DELAYED_WORK(&conn->disc_work, hci_conn_timeout);
INIT_DELAYED_WORK(&conn->auto_accept_work, hci_conn_auto_accept);
INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle);
INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout);
INIT_DELAYED_WORK() sets conn->disc_work.work.func = hci_conn_timeout.
- List Linkage:
__hci_conn_add() calls hci_conn_hash_add(hdev, conn), which inserts conn->list into hdev->conn_hash.list. The entry is only supposed to be removed when hci_conn_del() calls hci_conn_hash_del(hdev, conn).
- Lifetime: The object's underlying lifetime is tracked by
conn->dev.kref. The initial reference is taken in hci_conn_init_sysfs(). When the kref reaches zero, bt_link_release() (net/bluetooth/hci_sysfs.c:13) frees the object via kfree(conn).
4. Mechanism Leading to work->func == NULL
Because conn->disc_work.work.func is initialized to non-NULL upon creation and is never legitimately cleared to NULL while active, work->func == NULL indicates memory state corruption:
- Use-After-Free Hypothesis:
In
hci_conn_unlink(conn), if conn->parent != NULL and conn->link == NULL, the function returns early at line 1279 without dereferencing conn->hdev or conn->link_list. If an error or abort path (e.g., in LE pending connection / abort handlers such as hci_pend_le_actions_clear() or create_le_conn_complete()) invokes hci_conn_put() without calling hci_conn_del(), the initial reference from hci_conn_init_sysfs() is dropped. When conn->dev refcount reaches 0, bt_link_release() frees conn, but conn->list remains linked on hdev->conn_hash.list. If the slab slot is reallocated or zeroed, list_first_entry_or_null retrieves the stale entry, and disable_delayed_work_sync(&conn->disc_work) encounters zeroed fields.
- Intra-Object Memory Overwrite Hypothesis:
struct hci_conn is a large (~4.7 KB) slab object. KASAN does not detect buffer overflows between adjacent fields within the same slab allocation. Variable-length data buffers positioned immediately before disc_work (such as le_per_adv_data at offset 638, iso_qos at offset 2304, bis array at offset 2365, data_q at offset 2440, and tx_q at offset 2544) can overflow and overwrite conn->disc_work (offset 2640), corrupting work->func with zeros.
5. Caveats and Incomplete Findings
While the crash point, structure offsets, and list traversal mechanisms were verified directly from source code and crash register dumps, the exact preceding syscall sequence that caused conn->disc_work.work.func to become NULL (specifically distinguishing an unbalanced hci_conn_put() leading to a stale hash entry from an intra-object buffer overwrite of disc_work) could not be definitively isolated before research tools were disabled.