WARNING in disable_delayed_work_sync

Status: moderation: reported syz repro on 2026/11/09 09:03
Subsystems: bluetooth
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+a75074a07f21db09acdb@syzkaller.appspotmail.com
First crash: 1h43m, last: 1h43m
✨ AI-generated root cause summary:

When shutting down a Bluetooth HCI device (e.g., via vhci_release() during process exit), hci_dev_close_sync() calls hci_conn_hash_flush() to iterate and delete all active connections on hdev->conn_hash.list via hci_conn_del(). In hci_conn_del(), disable_delayed_work_sync(&conn->disc_work) invokes __flush_work(), which triggers WARN_ON(!work->func) because conn->disc_work.work.func is NULL. This occurs because the connection entry on conn_hash.list either suffered an in-place memory corruption overwriting disc_work or remained linked after an unbalanced hci_conn_put() dropped its refcount to zero without deleting it from the connection hash table.

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit 58c1eb84e977:

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:

  1. 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.
  2. 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.

▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
1e53dc0d assessment-security DenialOfService: ✅ Exploitable: ✅ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ✅ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING in disable_delayed_work_sync 2026/11/09 08:23 2026/11/09 08:23 2026/11/09 08:52 5cecaf1f

Sample crash report:
------------[ cut here ]------------
WARNING: kernel/workqueue.c:4570 at __flush_work+0x988/0xa90 kernel/workqueue.c:-1, CPU#0: syz-executor/4881
Modules linked in:
CPU: 0 UID: 0 PID: 4881 Comm: syz-executor Not tainted syzkaller #0 PREEMPT 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : __flush_work+0x988/0xa90 kernel/workqueue.c:-1
lr : __flush_work+0x984/0xa90 kernel/workqueue.c:4570
sp : ffff80009c5c7540
x29: ffff80009c5c76b0 x28: ffff7000138b8eac x27: 1fffe0001913694d
x26: ffff0000c89b4a68 x25: 0000000000000001 x24: dfff800000000000
x23: 0000000000000040 x22: dfff800000000000 x21: dfff800000000000
x20: ffff0000c89b4a50 x19: ffff80009c5c7580 x18: 00000000ffffffff
x17: 0000000000000003 x16: ffff8000898b7000 x15: ffff80008b586000
x14: 0000000000000000 x13: 0000000000000005 x12: 0000000000000000
x11: ffff7000138b8ec5 x10: 0000000000000000 x9 : 0000000000000000
x8 : 0000000000000000 x7 : 0000000000000000 x6 : 000000000000003f
x5 : 0000000000000040 x4 : 0000000000000000 x3 : 0000000000000020
x2 : 0000000000000008 x1 : ffff0000c8350000 x0 : 0000000000000000
Call trace:
 __flush_work+0x988/0xa90 kernel/workqueue.c:-1 (P)
 __cancel_work_sync+0xc8/0x144 kernel/workqueue.c:4729
 disable_delayed_work_sync+0x24/0x38 kernel/workqueue.c:4892
 hci_conn_del+0x6c/0xfcc net/bluetooth/hci_conn.c:1300
 hci_conn_hash_flush+0x17c/0x238 net/bluetooth/hci_conn.c:2814
 hci_dev_close_sync+0x5d4/0xd00 net/bluetooth/hci_sync.c:5681
 hci_dev_do_close+0x34/0xbc net/bluetooth/hci_core.c:502
 hci_unregister_dev+0x204/0x4c4 net/bluetooth/hci_core.c:2694
 vhci_release+0x12c/0x17c drivers/bluetooth/hci_vhci.c:704
 __fput+0x338/0x74c fs/file_table.c:512
 fput_close_sync+0x10c/0x278 fs/file_table.c:618
 filp_close_sync+0x7c/0xb4 fs/open.c:1573
 close_files fs/file.c:572 [inline]
 put_files_struct+0x1a4/0x2e8 fs/file.c:584
 switch_files_struct+0x60/0x74 fs/file.c:598
 exit_files+0x50/0x68 fs/file.c:604
 do_exit+0x55c/0x1a64 kernel/exit.c:977
 do_group_exit+0x198/0x238 kernel/exit.c:1123
 __do_sys_exit_group kernel/exit.c:1134 [inline]
 __se_sys_exit_group kernel/exit.c:1132 [inline]
 pid_child_should_wake+0x0/0x104 kernel/exit.c:1132
 __invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
 invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
 el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
 do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
 el0_svc+0x60/0x1d8 arch/arm64/kernel/entry-common.c:797
 el0t_64_sync_handler+0x44/0x104 arch/arm64/kernel/entry-common.c:816
 el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:606
irq event stamp: 175128
hardirqs last  enabled at (175127): [<ffff80008036fa20>] __cancel_work+0xf4/0x1a4 kernel/workqueue.c:4709
hardirqs last disabled at (175128): [<ffff800086953878>] el1_brk64+0x20/0x54 arch/arm64/kernel/entry-common.c:484
softirqs last  enabled at (173918): [<ffff8000800204f4>] __do_softirq+0x14/0x20 kernel/softirq.c:703

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/09 08:23 git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci 58c1eb84e977 5cecaf1f .config console log report syz / log [disk image] [vmlinux] [kernel image] ci-upstream-gce-arm64 WARNING in disable_delayed_work_sync
* Struck through repros no longer work on HEAD.