syzbot
INFO: task hung in vhost_task_stop (3)
Status:
upstream: reported C repro on 2026/11/12 23:43
Subsystems:
kernel
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+f59eab881ec20d423ab0@syzkaller.appspotmail.com
First crash: 4d10h, last: 4d10h
✨ AI-generated root cause summary:
When a KVM VM is initialized, kvm_mmu_start_lpage_recovery() creates the "kvm-nx-lpage-recovery" worker thread via vhost_task_create(). Because vhost_task_create() clones the calling process with CLONE_THREAD and .user_worker = 1 (PF_USER_WORKER) without PF_NOFREEZE, the worker inherits the caller's thread group and cgroup freezer state. If the cgroup is subsequently frozen (e.g. via cgroup1 freezer.state), the worker thread is trapped in __refrigerator() in the TASK_FROZEN state. When the VM file descriptor is closed, kvm_mmu_pre_destroy_vm() calls vhost_task_stop(), which sets VHOST_TASK_FLAGS_STOP, attempts to wake the worker using wake_up_process() (which only targets TASK_NORMAL), and blocks in wait_for_completion(&vtsk->exited). Because wake_up_process() cannot wake a task in TASK_FROZEN and freezing(current) remains true, the worker never exits, causing vhost_task_stop() to hang indefinitely in uninterruptible sleep.
▶
▼
✨ Full AI root cause analysis
Technical Root Cause Analysis
Involved Functions, Structs, and Files
kernel/vhost_task.c:
vhost_task_create(): Creates a user worker thread (PF_USER_WORKER) via copy_process() using CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_FS.
vhost_task_fn(): The main loop for the worker thread.
vhost_task_stop() / vhost_task_wake(): Stops the worker by setting VHOST_TASK_FLAGS_STOP, waking the task via wake_up_process(), and waiting on wait_for_completion(&vtsk->exited).
struct vhost_task: Represents the worker thread, its completion struct (exited), task pointer (task), and flags (flags).
arch/x86/kvm/mmu/mmu.c:
kvm_mmu_start_lpage_recovery(): Creates and starts the "kvm-nx-lpage-recovery" thread using vhost_task_create().
kvm_mmu_pre_destroy_vm(): Stops the recovery thread using vhost_task_stop().
kernel/freezer.c and kernel/signal.c:
freezing_slow_path() / freezing(): Evaluates whether a task should freeze.
fake_signal_wake_up() / get_signal(): Delivers freeze notifications and diverts the task to try_to_freeze().
__refrigerator(): Puts the task into TASK_FROZEN and calls schedule() in a loop while freezing(current) is true.
Bug Mechanism and Triggering Sequence
-
Thread Creation and Inheritance of Cgroup Freezer State:
During KVM VM initialization (ioctl(KVM_CREATE_VM) -> kvm_mmu_post_init_vm() -> kvm_mmu_start_lpage_recovery()), KVM spawns a background NX huge page recovery thread (kvm->arch.nx_huge_page_recovery_thread) by calling vhost_task_create().
vhost_task_create() configures struct kernel_clone_args with:
.flags = CLONE_FS | CLONE_UNTRACED | CLONE_VM | CLONE_THREAD | CLONE_SIGHAND,
.fn = vhost_task_fn,
.user_worker = 1,
Because CLONE_THREAD is passed, the worker is attached to the calling userspace task's thread group and shares the caller's cgroups (including any cgroup freezer). Crucially, the worker is marked with PF_USER_WORKER (not PF_KTHREAD) and does not have PF_NOFREEZE set.
-
Freezing the Worker:
When the cgroup is frozen (e.g., writing "FROZEN" to cgroup1's freezer.state), freeze_cgroup() -> freeze_task() runs. Since the worker lacks PF_KTHREAD, freeze_task() calls fake_signal_wake_up(p), setting TIF_SIGPENDING.
Inside vhost_task_fn(), the check if (signal_pending(current)) succeeds and calls get_signal(&ksig). Before processing normal signals, get_signal() calls try_to_freeze().
Because freezing(current) is true (due to cgroup1_freezing(current) and the absence of PF_NOFREEZE), the worker enters __refrigerator(false):
for (;;) {
raw_spin_lock_irq(¤t->pi_lock);
WRITE_ONCE(current->__state, TASK_FROZEN);
current->saved_state = TASK_RUNNING;
raw_spin_unlock_irq(¤t->pi_lock);
spin_lock_irq(&freezer_lock);
freeze = freezing(current) && !(check_kthr_stop && kthread_should_stop());
spin_unlock_irq(&freezer_lock);
if (!freeze)
break;
schedule();
}
The recovery worker sets its task state to TASK_FROZEN and sleeps in schedule().
-
Shutdown Deadlock:
When the userspace process closes the VM file descriptor or exits (close_range / fput -> kvm_vm_release -> kvm_destroy_vm -> kvm_arch_pre_destroy_vm -> kvm_mmu_pre_destroy_vm), KVM attempts to cleanly shut down the recovery thread by calling vhost_task_stop(kvm->arch.nx_huge_page_recovery_thread):
void vhost_task_stop(struct vhost_task *vtsk)
{
mutex_lock(&vtsk->exit_mutex);
if (!test_bit(VHOST_TASK_FLAGS_KILLED, &vtsk->flags)) {
set_bit(VHOST_TASK_FLAGS_STOP, &vtsk->flags);
vhost_task_wake(vtsk);
}
mutex_unlock(&vtsk->exit_mutex);
wait_for_completion(&vtsk->exited);
...
}
vhost_task_wake() invokes wake_up_process(vtsk->task), which calls try_to_wake_up(p, TASK_NORMAL, 0).
Because the worker's task state is TASK_FROZEN (0x00008000), which is not covered by TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE), try_to_wake_up() considers the task non-runnable and returns 0 without waking it. Furthermore, even if the task were temporarily woken, freezing(current) remains true, so __refrigerator() would immediately re-enter TASK_FROZEN and reschedule.
As a result, the worker never returns to vhost_task_fn() to check VHOST_TASK_FLAGS_STOP, complete &vtsk->exited, and exit. The calling thread in vhost_task_stop() hangs indefinitely in wait_for_completion() in state D (TASK_UNINTERRUPTIBLE), eventually triggering the hung task watchdog (khungtaskd).
Sample crash report:
INFO: task syz.0.17:5888 blocked for more than 143 seconds.
Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.0.17 state:D stack:26216 pid:5888 tgid:5888 ppid:5825 task_flags:0x400040 flags:0x08080002
Call Trace:
<TASK>
__schedu
context_switch kernel/sched/core.c:5583 [inline]
__schedule+0x17dd/0x5830 kernel/sched/core.c:7382
__schedule_loop kernel/sched/core.c:7459 [inline]
schedule+0x164/0x2b0 kernel/sched/core.c:7474
schedule_timeout+0xc0/0x2c0 kernel/time/sleep_timeout.c:75
do_wait_for_common kernel/sched/completion.c:100 [inline]
__wait_for_common kernel/sched/completion.c:121 [inline]
wait_for_common kernel/sched/completion.c:132 [inline]
wait_for_completion+0x2c5/0x5d0 kernel/sched/completion.c:153
vhost_task_stop+0xc0/0x170 kernel/vhost_task.c:102
kvm_arch_pre_destroy_vm+0x68/0x80 arch/x86/kvm/x86.c:10107
kvm_destroy_vm virt/kvm/kvm_main.c:1284 [inline]
kvm_put_kvm+0x377/0xb40 virt/kvm/kvm_main.c:1350
kvm_vm_release+0x6b/0x80 virt/kvm/kvm_main.c:1375
__fput+0x408/0xa40 fs/file_table.c:512
fput_close_sync+0x11f/0x240 fs/file_table.c:618
filp_close_sync+0x6e/0xa0 fs/open.c:1574
__range_close fs/file.c:919 [inline]
__do_sys_close_range fs/file.c:1003 [inline]
__se_sys_close_range+0xa77/0xef0 fs/file.c:954
do_syscall_32_irqs_on arch/x86/entry/syscall_32.c:79 [inline]
__do_fast_syscall_32+0x28b/0x6c0 arch/x86/entry/syscall_32.c:282
do_fast_syscall_32+0x33/0x70 arch/x86/entry/syscall_32.c:307
entry_SYSENTER_compat_after_hwframe+0x84/0x8e
RIP: 0023:0xf7fb3f1c
RSP: 002b:00000000ffe837dc EFLAGS: 00000206 ORIG_RAX: 00000000000001b4
RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 000000000000001e
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000206 R12: 0000000000000000
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
</TASK>
Showing all locks held in the system:
locks held by khungtaskd/33: 1, last CPU#0:
#0: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6871
locks held by kworker/u8:9/1115: 4, last CPU#0:
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3460 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3578
#1: ffff8880b8224708 (psi_seq){-.-.}-{0:0}, at: psi_task_switch+0x57/0x7d0 kernel/sched/psi.c:928
#2: ffff8880b8226498 (&base->lock){-.-.}-{2:2}, at: lock_timer_base kernel/time/timer.c:1005 [inline]
#2: ffff8880b8226498 (&base->lock){-.-.}-{2:2}, at: __mod_timer+0x1a9/0xed0 kernel/time/timer.c:1086
#3: ffffffff9c12ba00 (&obj_hash[i].lock){-.-.}-{2:2}, at: debug_object_activate+0xb7/0x420 lib/debugobjects.c:873
locks held by kworker/u8:12/1168: 3, last CPU#0:
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3460 [inline]
#0: ffff88813fe4c148 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3578
#1: ffffc900050f7c40 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#1: ffffc900050f7c40 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#1: ffffc900050f7c40 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3460 [inline]
#1: ffffc900050f7c40 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3578
#2: ffff888037387258 (&devlink->lock_key#9){+.+.}-{4:4}, at: nsim_dev_trap_report_work+0x57/0xb30 drivers/net/netdevsim/dev.c:834
locks held by kworker/0:2/1216: 3, last CPU#0:
#0: ffff88813fe0dd48 ((wq_completion)events_power_efficient){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff88813fe0dd48 ((wq_completion)events_power_efficient){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff88813fe0dd48 ((wq_completion)events_power_efficient){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3460 [inline]
#0: ffff88813fe0dd48 ((wq_completion)events_power_efficient){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3578
#1: ffffc90005487c40 ((work_completion)(&(&gc_work->dwork)->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#1: ffffc90005487c40 ((work_completion)(&(&gc_work->dwork)->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#1: ffffc90005487c40 ((work_completion)(&(&gc_work->dwork)->work)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3460 [inline]
#1: ffffc90005487c40 ((work_completion)(&(&gc_work->dwork)->work)){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3578
#2: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#2: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#2: ffffffff9015a260 (rcu_read_lock){....}-{1:3}, at: gc_worker+0x274/0x1890 net/netfilter/nf_conntrack_core.c:1530
locks held by getty/5391: 2, on CPU#1:
#0: ffff8880385230a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
#1: ffffc900032362e8 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x45a/0x1360 drivers/tty/n_tty.c:2211
=============================================
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 33 Comm: khungtaskd Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/15/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
nmi_cpu_backtrace+0x274/0x2d0 lib/nmi_backtrace.c:123
nmi_trigger_cpumask_backtrace+0x17d/0x390 lib/nmi_backtrace.c:66
trigger_all_cpu_backtrace include/linux/nmi.h:164 [inline]
__sys_info lib/sys_info.c:157 [inline]
sys_info+0x135/0x170 lib/sys_info.c:165
check_hung_uninterruptible_tasks kernel/hung_task.c:377 [inline]
watchdog+0x1073/0x10f0 kernel/hung_task.c:598
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 32 Comm: pr/ttyS0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/15/2026
RIP: 0010:io_serial_in+0x77/0xc0 drivers/tty/serial/8250/8250_port.c:425
Code: e8 2e 6a 43 fc 44 89 f9 d3 e3 49 83 c6 40 4c 89 f0 48 c1 e8 03 42 80 3c 20 00 74 08 4c 89 f7 e8 2f f3 ba fc 41 03 1e 89 da ec <0f> b6 c0 5b 41 5c 41 5e 41 5f e9 9a 14 83 06 cc 44 89 f9 80 e1 07
RSP: 0018:ffffc90000a878b8 EFLAGS: 00000002
RAX: 1ffffffff3840b00 RBX: 00000000000003fd RCX: 0000000000000000
RDX: 00000000000003fd RSI: 0000000000000000 RDI: 0000000000000020
RBP: dffffc0000000000 R08: 0000000000000003 R09: 0000000000000004
R10: dffffc0000000000 R11: ffffffff8585a440 R12: dffffc0000000000
R13: 0000000000000000 R14: ffffffff9c205ec0 R15: 0000000000000000
FS: 0000000000000000(0000) GS:ffff888123409000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000563e7aa97d08 CR3: 000000000ff48000 CR4: 00000000003526f0
Call Trace:
<TASK>
serial_in drivers/tty/serial/8250/8250.h:134 [inline]
serial_lsr_in drivers/tty/serial/8250/8250.h:156 [inline]
wait_for_lsr+0x184/0x2f0 drivers/tty/serial/8250/8250_port.c:2036
serial8250_fifo_wait_for_lsr_thre drivers/tty/serial/8250/8250_port.c:3294 [inline]
__serial8250_console_fifo_write+0xcd/0x3e0 drivers/tty/serial/8250/8250_port.c:3369
serial8250_console_fifo_write drivers/tty/serial/8250/8250_port.c:3402 [inline]
__serial8250_console_write drivers/tty/serial/8250/8250_port.c:3456 [inline]
serial8250_console_write+0xa7e/0x1200 drivers/tty/serial/8250/8250_port.c:3520
nbcon_emit_next_record+0xef5/0x1a90 kernel/printk/nbcon.c:-1
nbcon_emit_one kernel/printk/nbcon.c:1157 [inline]
nbcon_kthread_func+0x6d9/0x8d0 kernel/printk/nbcon.c:1271
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
* Struck through repros no longer work on HEAD.