From: Cássio Gabriel <cassiogabrielcontato@gmail.com> mainline inclusion from mainline-v7.2-rc1 commit b9bdd68b8b979c7e9de58b2e7d21e1d7d932c755 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18716 CVE: CVE-2026-80872 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- request_firmware_nowait() keeps the callback module pinned and holds a device reference until the firmware work completes. Callers still have no way to cancel or synchronize the queued callback before tearing down their driver-private state. Track scheduled async firmware work in an internal list and add request_firmware_nowait_cancel(). The helper cancels work matching the device, callback context and callback function. It cancels work that has not started yet and waits for an already-running callback to return. If the request has already completed, it is a no-op. Keep the existing request_firmware_nowait() lifetime model manual. A devres-managed variant can be layered on top separately if needed. Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com> Reviewed-by: Takashi Iwai <tiwai@suse.de> Acked-by: Danilo Krummrich <dakr@kernel.org> Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://patch.msgid.link/20260505-alsa-hda-tas2781-fw-callback-teardown-v4-1... Conflicts: drivers/base/firmware_loader/main.c [The prerequisite patch 11c63e57404e ("firmware: add nowarn variant of request_firmware_nowait()") changed request_firmware_nowait to _request_firmware_nowait, thereby causing a conflict. This patch is intended to introduce a nowarn variant of this function and does not need to be backported.] Signed-off-by: Tengda Wu <wutengda2@huawei.com> --- drivers/base/firmware_loader/main.c | 69 ++++++++++++++++++++++++++--- include/linux/firmware.h | 10 +++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 4f4614811d09..6be766f67cbb 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -1171,6 +1171,7 @@ EXPORT_SYMBOL(release_firmware); /* Async support */ struct firmware_work { struct work_struct work; + struct list_head list; struct module *module; const char *name; struct device *device; @@ -1179,6 +1180,17 @@ struct firmware_work { u32 opt_flags; }; +static LIST_HEAD(firmware_work_list); +static DEFINE_SPINLOCK(firmware_work_lock); + +static void firmware_work_free(struct firmware_work *fw_work) +{ + put_device(fw_work->device); /* taken in request_firmware_nowait() */ + module_put(fw_work->module); + kfree_const(fw_work->name); + kfree(fw_work); +} + static void request_firmware_work_func(struct work_struct *work) { struct firmware_work *fw_work; @@ -1189,11 +1201,14 @@ static void request_firmware_work_func(struct work_struct *work) _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0, fw_work->opt_flags); fw_work->cont(fw, fw_work->context); - put_device(fw_work->device); /* taken in request_firmware_nowait() */ - - module_put(fw_work->module); - kfree_const(fw_work->name); - kfree(fw_work); + spin_lock_irq(&firmware_work_lock); + if (!list_empty(&fw_work->list)) { + list_del_init(&fw_work->list); + spin_unlock_irq(&firmware_work_lock); + firmware_work_free(fw_work); + return; + } + spin_unlock_irq(&firmware_work_lock); } /** @@ -1226,6 +1241,7 @@ request_firmware_nowait( void (*cont)(const struct firmware *fw, void *context)) { struct firmware_work *fw_work; + unsigned long flags; fw_work = kzalloc(sizeof(struct firmware_work), gfp); if (!fw_work) @@ -1257,11 +1273,54 @@ request_firmware_nowait( get_device(fw_work->device); INIT_WORK(&fw_work->work, request_firmware_work_func); + + spin_lock_irqsave(&firmware_work_lock, flags); + list_add_tail(&fw_work->list, &firmware_work_list); schedule_work(&fw_work->work); + spin_unlock_irqrestore(&firmware_work_lock, flags); + return 0; } EXPORT_SYMBOL(request_firmware_nowait); +/** + * request_firmware_nowait_cancel() - cancel an async firmware request + * @device: device for which the firmware is being loaded + * @context: context passed to request_firmware_nowait() + * @cont: callback passed to request_firmware_nowait() + * + * Cancel a pending request_firmware_nowait() request for @device, @context + * and @cont. If the associated work has already started, this function waits + * until the callback has returned. If the callback has already completed, this + * function does nothing. + * + * This function may sleep. + */ +void request_firmware_nowait_cancel(struct device *device, void *context, + void (*cont)(const struct firmware *fw, + void *context)) +{ + struct firmware_work *fw_work = NULL; + struct firmware_work *tmp; + + spin_lock_irq(&firmware_work_lock); + list_for_each_entry_reverse(tmp, &firmware_work_list, list) { + if (tmp->device == device && tmp->context == context && + tmp->cont == cont) { + fw_work = tmp; + list_del_init(&fw_work->list); + break; + } + } + spin_unlock_irq(&firmware_work_lock); + + if (!fw_work) + return; + cancel_work_sync(&fw_work->work); + firmware_work_free(fw_work); +} +EXPORT_SYMBOL_GPL(request_firmware_nowait_cancel); + #ifdef CONFIG_FW_CACHE static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); diff --git a/include/linux/firmware.h b/include/linux/firmware.h index 2035a0d9cc86..bc1d02980626 100644 --- a/include/linux/firmware.h +++ b/include/linux/firmware.h @@ -104,6 +104,9 @@ int request_firmware_nowait( struct module *module, bool uevent, const char *name, struct device *device, gfp_t gfp, void *context, void (*cont)(const struct firmware *fw, void *context)); +void request_firmware_nowait_cancel(struct device *device, void *context, + void (*cont)(const struct firmware *fw, + void *context)); int request_firmware_direct(const struct firmware **fw, const char *name, struct device *device); int request_firmware_into_buf(const struct firmware **firmware_p, @@ -143,6 +146,13 @@ static inline int request_firmware_nowait( return -EINVAL; } +static inline void request_firmware_nowait_cancel(struct device *device, + void *context, + void (*cont)(const struct firmware *fw, + void *context)) +{ +} + static inline void release_firmware(const struct firmware *fw) { } -- 2.34.1