Say a SQPOLL thread is a member of a thread-group that coredumps. The coredump code uses zap_process() and sends SIGKILL. The SQPOLL thread uses io_sqd_handle_event() and calls get_signal(). It removes SIGKILL from the pending set and returns. The SQPOLL thread breaks out of the loop and drains its own task work. Any pending io_req_task_submit() with REQ_F_FORCE_ASYNC creates a new worker when no other worker is free. So it ends up calling create_io_thread() from a thread whose fatal signal is gone. That means copy_process() allows the creation. It's also possible for an exiting io-wq worker to push new work onto the SQPOLL thread. zap_threads() counts the number of coredumping threads. The coredump client waits in coredump_wait_inactive() until all threads in the thread-group are parked. The new thread exits right away because the workqueue is going down. It inherited PF_SIGNALED from its creator so it links itself onto core_state->tasks in coredump_task_exit(). The problem is that it then decrements "threads_remaining" even though zap_process() never actually counted the new thread. So the count goes to zero too early. So either the coredump misses the thread or it dumps a thread that is still alive. The same race exists during exec. de_thread() zaps the other threads the same way and counts them in signal->notify_count, and a zapped user worker that has already dequeued its SIGKILL can still clone while de_thread() waits for that count. And once de_thread() has cleared signal->group_exec_task the exec'ing thread itself runs task work in io_uring_task_cancel() and a pending create_worker_cb() creates a thread after the group was made single-threaded. Close all of that in copy_process(). Refuse to create a thread while: (1) SIGNAL_GROUP_EXIT is set (set together with core_state by zap_process() (2) signal->group_exec_task is set (3) while current is in execve Conditions (1) and (2) are handled with siglock help which means copy_process() and zap_process() synchronize on it. create_io_thread() treats the failure as a failed task creation and doesn't retry. Fixes: 3bfe6106693b ("io-wq: fork worker threads from original task") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 10be4a0ecb3f..6cd167a2b27b 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2491,8 +2491,10 @@ __latent_entropy struct task_struct *copy_process( goto bad_fork_core_free; } - /* Let kill terminate clone/fork in the middle */ - if (fatal_signal_pending(current)) { + /* Let kill or a group exit, exec or coredump abort clone/fork */ + if (fatal_signal_pending(current) || + (current->signal->flags & SIGNAL_GROUP_EXIT) || + current->signal->group_exec_task || current->in_execve) { retval = -EINTR; goto bad_fork_core_free; } -- 2.53.0