]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
workqueue: fix devm_alloc_workqueue() va_list misuse
authorBreno Leitao <leitao@debian.org>
Tue, 28 Apr 2026 15:10:43 +0000 (08:10 -0700)
committerTejun Heo <tj@kernel.org>
Tue, 28 Apr 2026 16:13:40 +0000 (06:13 -1000)
devm_alloc_workqueue() built a va_list and passed it as a single
positional argument to the variadic alloc_workqueue() macro:

va_start(args, max_active);
wq = alloc_workqueue(fmt, flags, max_active, args);
va_end(args);

C does not allow forwarding a va_list through a ... parameter.
alloc_workqueue() expands to alloc_workqueue_noprof(), which runs
its own va_start() over its ... params, so the inner
vsnprintf(wq->name, sizeof(wq->name), fmt, args) in
__alloc_workqueue() received the outer va_list object as the first
variadic slot rather than the caller's actual format arguments.

Add a new static helper alloc_workqueue_va() that wraps
__alloc_workqueue() and runs wq_init_lockdep() on success, and
fold both alloc_workqueue_noprof() and devm_alloc_workqueue_noprof()
onto it as suggested by Tejun.

The wq_init_lockdep() step is required on the devm path
too, otherwise __flush_workqueue()'s on-stack
COMPLETION_INITIALIZER_ONSTACK_MAP would NULL-deref wq->lockdep_map.

No caller changes are required. devm_alloc_ordered_workqueue() is
a macro forwarding to devm_alloc_workqueue() and inherits the fix.
Two in-tree callers actively trigger the broken path on every probe:

  drivers/power/supply/mt6370-charger.c:889
  drivers/power/supply/max77705_charger.c:649

both of which use devm_alloc_ordered_workqueue(dev, "%s", 0,
dev_name(dev)).

A standalone reproducer module is available at[1].

Link: https://github.com/leitao/debug/blob/main/workqueue/valist/wq_va_test.c
Fixes: 1dfc9d60a69e ("workqueue: devres: Add device-managed allocate workqueue")
Signed-off-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
include/linux/workqueue.h
kernel/workqueue.c

index ab6cb70ca1a52cf93d8e717115e97c245fbc04e2..6177624539b3b35027ef4900eb0fa3490be392c9 100644 (file)
@@ -534,8 +534,10 @@ alloc_workqueue_noprof(const char *fmt, unsigned int flags, int max_active, ...)
  * Pointer to the allocated workqueue on success, %NULL on failure.
  */
 __printf(2, 5) struct workqueue_struct *
-devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,
-                    int max_active, ...);
+devm_alloc_workqueue_noprof(struct device *dev, const char *fmt,
+                           unsigned int flags, int max_active, ...);
+#define devm_alloc_workqueue(...)      \
+       alloc_hooks(devm_alloc_workqueue_noprof(__VA_ARGS__))
 
 #ifdef CONFIG_LOCKDEP
 /**
index 5f747f241a5f1e151c071b7357d033492250bbdc..24d0265191d464ea1434dff36bc9962e2be093db 100644 (file)
@@ -5906,6 +5906,20 @@ err_destroy:
        return NULL;
 }
 
+static struct workqueue_struct *alloc_workqueue_va(const char *fmt,
+                                                  unsigned int flags,
+                                                  int max_active,
+                                                  va_list args)
+{
+       struct workqueue_struct *wq;
+
+       wq = __alloc_workqueue(fmt, flags, max_active, args);
+       if (wq)
+               wq_init_lockdep(wq);
+
+       return wq;
+}
+
 __printf(1, 4)
 struct workqueue_struct *alloc_workqueue_noprof(const char *fmt,
                                                unsigned int flags,
@@ -5915,12 +5929,8 @@ struct workqueue_struct *alloc_workqueue_noprof(const char *fmt,
        va_list args;
 
        va_start(args, max_active);
-       wq = __alloc_workqueue(fmt, flags, max_active, args);
+       wq = alloc_workqueue_va(fmt, flags, max_active, args);
        va_end(args);
-       if (!wq)
-               return NULL;
-
-       wq_init_lockdep(wq);
 
        return wq;
 }
@@ -5932,15 +5942,15 @@ static void devm_workqueue_release(void *res)
 }
 
 __printf(2, 5) struct workqueue_struct *
-devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,
-                    int max_active, ...)
+devm_alloc_workqueue_noprof(struct device *dev, const char *fmt,
+                           unsigned int flags, int max_active, ...)
 {
        struct workqueue_struct *wq;
        va_list args;
        int ret;
 
        va_start(args, max_active);
-       wq = alloc_workqueue(fmt, flags, max_active, args);
+       wq = alloc_workqueue_va(fmt, flags, max_active, args);
        va_end(args);
        if (!wq)
                return NULL;
@@ -5951,7 +5961,7 @@ devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,
 
        return wq;
 }
-EXPORT_SYMBOL_GPL(devm_alloc_workqueue);
+EXPORT_SYMBOL_GPL(devm_alloc_workqueue_noprof);
 
 #ifdef CONFIG_LOCKDEP
 __printf(1, 5)