History

workqueue: fix devm_alloc_workqueue() va_list misuse

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].

Breno Leitao committed 6 months ago

Commit 0de4cb41 parent dca922e2 files · +23 −11Browse files

Unified · Split

2 files
  1. include/linux/workqueue.h +4 −2
  2. kernel/workqueue.c +19 −9
include/linux/workqueue.h+4−2
@@ -534,8 +534,10 @@ alloc_workqueue_noprof(const char *fmt, unsigned int flags, int max_active, ...) · 533 unmodified lines
534 * Pointer to the allocated workqueue on success, %NULL on failure.534 * Pointer to the allocated workqueue on success, %NULL on failure.
535 */535 */
536__printf(2, 5) struct workqueue_struct *536__printf(2, 5) struct workqueue_struct *
537devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,537devm_alloc_workqueue_noprof(struct device *dev, const char *fmt,
538 int max_active, ...);538 unsigned int flags, int max_active, ...);
539#define devm_alloc_workqueue(...) \
540 alloc_hooks(devm_alloc_workqueue_noprof(__VA_ARGS__))
539 541
540#ifdef CONFIG_LOCKDEP542#ifdef CONFIG_LOCKDEP
541/**543/**
kernel/workqueue.c+19−9
@@ -5906,6 +5906,20 @@ err_destroy: · 5905 unmodified lines
5906 return NULL;5906 return NULL;
5907}5907}
5908 5908
5909static struct workqueue_struct *alloc_workqueue_va(const char *fmt,
5910 unsigned int flags,
5911 int max_active,
5912 va_list args)
5913{
5914 struct workqueue_struct *wq;
5915
5916 wq = __alloc_workqueue(fmt, flags, max_active, args);
5917 if (wq)
5918 wq_init_lockdep(wq);
5919
5920 return wq;
5921}
5922
5909__printf(1, 4)5923__printf(1, 4)
5910struct workqueue_struct *alloc_workqueue_noprof(const char *fmt,5924struct workqueue_struct *alloc_workqueue_noprof(const char *fmt,
5911 unsigned int flags,5925 unsigned int flags,
@@ -5915,12 +5929,8 @@ struct workqueue_struct *alloc_workqueue_noprof(const char *fmt, · 3 unmodified lines
5915 va_list args;5929 va_list args;
5916 5930
5917 va_start(args, max_active);5931 va_start(args, max_active);
5918 wq = __alloc_workqueue(fmt, flags, max_active, args);5932 wq = alloc_workqueue_va(fmt, flags, max_active, args);
5919 va_end(args);5933 va_end(args);
5920 if (!wq)
5921 return NULL;
5922
5923 wq_init_lockdep(wq);
5924 5934
5925 return wq;5935 return wq;
5926}5936}
@@ -5932,15 +5942,15 @@ static void devm_workqueue_release(void *res) · 5 unmodified lines
5932}5942}
5933 5943
5934__printf(2, 5) struct workqueue_struct *5944__printf(2, 5) struct workqueue_struct *
5935devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,5945devm_alloc_workqueue_noprof(struct device *dev, const char *fmt,
5936 int max_active, ...)5946 unsigned int flags, int max_active, ...)
5937{5947{
5938 struct workqueue_struct *wq;5948 struct workqueue_struct *wq;
5939 va_list args;5949 va_list args;
5940 int ret;5950 int ret;
5941 5951
5942 va_start(args, max_active);5952 va_start(args, max_active);
5943 wq = alloc_workqueue(fmt, flags, max_active, args);5953 wq = alloc_workqueue_va(fmt, flags, max_active, args);
5944 va_end(args);5954 va_end(args);
5945 if (!wq)5955 if (!wq)
5946 return NULL;5956 return NULL;
@@ -5951,7 +5961,7 @@ devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags, · 4 unmodified lines
5951 5961
5952 return wq;5962 return wq;
5953}5963}
5954EXPORT_SYMBOL_GPL(devm_alloc_workqueue);5964EXPORT_SYMBOL_GPL(devm_alloc_workqueue_noprof);
5955 5965
5956#ifdef CONFIG_LOCKDEP5966#ifdef CONFIG_LOCKDEP
5957__printf(1, 5)5967__printf(1, 5)