mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
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].
Link: https://github.com/leitao/debug/blob/main/workqueue/valist/wq_va_test.c [1]
Fixes: 1dfc9d60a6 ("workqueue: devres: Add device-managed allocate workqueue")
Signed-off-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
parent
dca922e019
commit
0de4cb473a
|
|
@ -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
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5906,6 +5906,20 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
|
|||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user