summaryrefslogtreecommitdiff
path: root/kernel/workqueue.c
diff options
context:
space:
mode:
authorBreno Leitao <leitao@debian.org>2026-03-23 03:18:36 -0700
committerTejun Heo <tj@kernel.org>2026-03-25 05:52:01 -1000
commitafeaa9f2532d1d8d04803d09ac2d4f7107854f29 (patch)
treee7ce468999845444b874834d257ba8ef36f9696d /kernel/workqueue.c
parent48718378ab1f80da847930224360f8a1b690a538 (diff)
workqueue: unlink pwqs from wq->pwqs list in alloc_and_link_pwqs() error path
When alloc_and_link_pwqs() fails partway through the per-cpu allocation loop, some pool_workqueues may have already been linked into wq->pwqs via link_pwq(). The error path frees these pwqs with kmem_cache_free() but never removes them from the wq->pwqs list, leaving dangling pointers in the list. Currently this is not exploitable because the workqueue was never added to the global workqueues list and the caller frees the wq immediately after. However, this makes sure that alloc_and_link_pwqs() doesn't leave any half-baked structure, which may have side effects if not properly cleaned up. Fix this by unlinking each pwq from wq->pwqs before freeing it. No locking is needed as the workqueue has not been published yet, thus no concurrency is possible. Signed-off-by: Breno Leitao <leitao@debian.org> Signed-off-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'kernel/workqueue.c')
-rw-r--r--kernel/workqueue.c10
1 files changed, 9 insertions, 1 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 63acaa3e1d6a..4f543da2e7c0 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5624,8 +5624,16 @@ enomem:
for_each_possible_cpu(cpu) {
struct pool_workqueue *pwq = *per_cpu_ptr(wq->cpu_pwq, cpu);
- if (pwq)
+ if (pwq) {
+ /*
+ * Unlink pwq from wq->pwqs since link_pwq()
+ * may have already added it. wq->mutex is not
+ * needed as the wq has not been published yet.
+ */
+ if (!list_empty(&pwq->pwqs_node))
+ list_del_rcu(&pwq->pwqs_node);
kmem_cache_free(pwq_cache, pwq);
+ }
}
free_percpu(wq->cpu_pwq);
wq->cpu_pwq = NULL;