In modules which extensively use devm_ resource management, it is often easy to overlook (delayed) work that is left pending or running after the module is unloaded. This could introduce user-after-free issues.
Nudge kernel developers into 'doing the right thing' by introducing a resource-managed version of INIT_[DELAYED_]WORK(). This can be used as an elegant way to ensure that work is not left pending or running after its dependencies are released. Functions introduced in workqueue.h : - devm_init_work() - devm_init_delayed_work() Signed-off-by: Sven Van Asbroeck <thesve...@googlemail.com> --- include/linux/workqueue.h | 7 +++++ kernel/workqueue.c | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index 60d673e15632..eee148eb9908 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -15,6 +15,7 @@ #include <linux/cpumask.h> #include <linux/rcupdate.h> +struct device; struct workqueue_struct; struct work_struct; @@ -670,4 +671,10 @@ int workqueue_offline_cpu(unsigned int cpu); int __init workqueue_init_early(void); int __init workqueue_init(void); +int __must_check devm_init_work(struct device *dev, struct work_struct *work, + work_func_t func); +int __must_check devm_init_delayed_work(struct device *dev, + struct delayed_work *dw, + work_func_t func); + #endif diff --git a/kernel/workqueue.c b/kernel/workqueue.c index fc5d23d752a5..ab814b0b6c81 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5837,3 +5837,57 @@ int __init workqueue_init(void) return 0; } + +static void devm_work_release(void *data) +{ + struct work_struct *work = data; + + cancel_work_sync(work); +} + +/** + * devm_init_work - resource-controlled version of INIT_WORK() + * @dev: valid struct device pointer + * @work: work pointer to initialize + * @func: work function to initialize 'work' with + * + * Initialize the work pointer just like INIT_WORK(), but use resource control + * to help ensure work is not left running or pending when dev is destroyed. + * + * Return: 0 on success, -errno on failure. + */ +int __must_check devm_init_work(struct device *dev, struct work_struct *work, + work_func_t func) +{ + INIT_WORK(work, func); + return devm_add_action(dev, devm_work_release, work); +} +EXPORT_SYMBOL_GPL(devm_init_work); + +static void devm_delayed_work_release(void *data) +{ + struct delayed_work *dw = data; + + cancel_delayed_work_sync(dw); +} + +/** + * devm_init_delayed_work - resource-controlled version of INIT_DELAYED_WORK() + * @dev: valid struct device pointer + * @dw: delayed_work pointer to initialize + * @func: work function to initialize 'dw' with + * + * Initialize the delayed_work pointer just like INIT_DELAYED_WORK(), but use + * resource control to help ensure delayed work is not left running or pending + * when dev is destroyed. + * + * Return: 0 on success, -errno on failure. + */ +int __must_check devm_init_delayed_work(struct device *dev, + struct delayed_work *dw, + work_func_t func) +{ + INIT_DELAYED_WORK(dw, func); + return devm_add_action(dev, devm_delayed_work_release, dw); +} +EXPORT_SYMBOL_GPL(devm_init_delayed_work); -- 2.17.1