* [PATCH 0/5] module/dups: Fix a use-after-free and improve the code
@ 2026-07-20 12:15 Petr Pavlu
2026-07-20 12:15 ` [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling Petr Pavlu
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
Fix a use-after-free in the kmod dups code, simplify how duplicate requests are
informed about the result, and make several general improvements to the code.
Petr Pavlu (5):
module/dups: Fix use-after-free in kmod_dup_req lifetime handling
module/dups: Inform duplicate requests about the result directly
module/dups: Use scoped guards for kmod_dup_mutex
module/dups: Use strcmp() to compare module names
module/dups: Clean up includes
kernel/module/dups.c | 143 +++++++++++++++++++------------------------
1 file changed, 62 insertions(+), 81 deletions(-)
base-commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa
--
2.54.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
@ 2026-07-20 12:15 ` Petr Pavlu
2026-07-20 12:32 ` sashiko-bot
2026-07-20 12:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
The kmod dups code uses RCU to ensure that a kmod_dup_req instance is freed
only after it is no longer referenced. When releasing an instance, the
kmod_dup_request_delete() function removes the kmod_dup_req from the
dup_kmod_reqs list, waits via synchronize_rcu() and finally frees it.
However, this doesn't work correctly because parallel users referencing the
instance in kmod_dup_request_exists_wait() don't enter an RCU read-side
critical section. This can result in a use-after-free.
The kmod_dup_request_exists_wait() function may need to hold a valid
reference to a kmod_dup_req instance across a blocking wait until the
corresponding modprobe command completes. This makes it unsuitable for RCU.
Fix the issue by changing the lifecycle management of kmod_dup_req to use
reference counting.
Fixes: 8660484ed1cf ("module: add debugging auto-load duplicate module support")
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
kernel/module/dups.c | 43 ++++++++++++++++++++++++++++++++-----------
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index 1d720a5311ba..e1828e865edd 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -30,6 +30,7 @@
#include <linux/ptrace.h>
#include <linux/async.h>
#include <linux/uaccess.h>
+#include <linux/refcount.h>
#include "internal.h"
@@ -38,13 +39,12 @@
static bool enable_dups_trace = IS_ENABLED(CONFIG_MODULE_DEBUG_AUTOLOAD_DUPS_TRACE);
module_param(enable_dups_trace, bool_enable_only, 0644);
-/*
- * Protects dup_kmod_reqs list, adds / removals with RCU.
- */
+/* A mutex-protected list of active kmod requests. */
static DEFINE_MUTEX(kmod_dup_mutex);
static LIST_HEAD(dup_kmod_reqs);
struct kmod_dup_req {
+ refcount_t refcount;
struct list_head list;
char name[MODULE_NAME_LEN];
struct completion first_req_done;
@@ -53,12 +53,24 @@ struct kmod_dup_req {
int dup_ret;
};
+static void get_kmod_req(struct kmod_dup_req *kmod_req)
+{
+ refcount_inc(&kmod_req->refcount);
+}
+
+static void put_kmod_req(struct kmod_dup_req *kmod_req)
+{
+ if (refcount_dec_and_test(&kmod_req->refcount))
+ kfree(kmod_req);
+}
+
static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name)
{
struct kmod_dup_req *kmod_req;
- list_for_each_entry_rcu(kmod_req, &dup_kmod_reqs, list,
- lockdep_is_held(&kmod_dup_mutex)) {
+ lockdep_assert_held(&kmod_dup_mutex);
+
+ list_for_each_entry(kmod_req, &dup_kmod_reqs, list) {
if (strlen(kmod_req->name) == strlen(module_name) &&
!memcmp(kmod_req->name, module_name, strlen(module_name))) {
return kmod_req;
@@ -87,10 +99,10 @@ static void kmod_dup_request_delete(struct work_struct *work)
* just returning 0.
*/
mutex_lock(&kmod_dup_mutex);
- list_del_rcu(&kmod_req->list);
- synchronize_rcu();
+ list_del(&kmod_req->list);
mutex_unlock(&kmod_dup_mutex);
- kfree(kmod_req);
+
+ put_kmod_req(kmod_req);
}
static void kmod_dup_request_complete(struct work_struct *work)
@@ -129,6 +141,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
if (!new_kmod_req)
return false;
+ refcount_set(&new_kmod_req->refcount, 1);
memcpy(new_kmod_req->name, module_name, strlen(module_name));
INIT_WORK(&new_kmod_req->complete_work, kmod_dup_request_complete);
INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
@@ -161,10 +174,12 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
* keep tab on duplicates later.
*/
pr_debug("New request_module() for %s\n", module_name);
- list_add_rcu(&new_kmod_req->list, &dup_kmod_reqs);
+ list_add(&new_kmod_req->list, &dup_kmod_reqs);
mutex_unlock(&kmod_dup_mutex);
return false;
}
+
+ get_kmod_req(kmod_req);
mutex_unlock(&kmod_dup_mutex);
/* We are dealing with a duplicate request now */
@@ -194,7 +209,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
* calls bail out right away.
*/
*dup_ret = 0;
- return true;
+ goto out;
}
/*
@@ -209,12 +224,14 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
TASK_KILLABLE);
if (ret) {
*dup_ret = ret;
- return true;
+ goto out;
}
/* Now the duplicate request has the same exact return value as the first request */
*dup_ret = kmod_req->dup_ret;
+out:
+ put_kmod_req(kmod_req);
return true;
}
@@ -224,6 +241,10 @@ void kmod_dup_request_announce(char *module_name, int ret)
mutex_lock(&kmod_dup_mutex);
+ /*
+ * Find the entry previously added in kmod_dup_request_exists_wait()
+ * that is owned by the current task.
+ */
kmod_req = kmod_dup_request_lookup(module_name);
if (!kmod_req)
goto out;
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/5] module/dups: Inform duplicate requests about the result directly
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
2026-07-20 12:15 ` [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling Petr Pavlu
@ 2026-07-20 12:15 ` Petr Pavlu
2026-07-20 12:28 ` sashiko-bot
2026-07-20 12:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex Petr Pavlu
` (2 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
When kmod_dup_request_announce() announces the completion of
a request_module() call to duplicate waiters, it queues a work item to
invoke kmod_dup_request_complete(), and only that function calls
complete_all().
This adds an arbitrary delay that is unnecessary and provides little
benefit. Call complete_all() directly from kmod_dup_request_announce()
instead.
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
kernel/module/dups.c | 50 +++++++++++---------------------------------
1 file changed, 12 insertions(+), 38 deletions(-)
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index e1828e865edd..f0bdd7560d7e 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -48,7 +48,6 @@ struct kmod_dup_req {
struct list_head list;
char name[MODULE_NAME_LEN];
struct completion first_req_done;
- struct work_struct complete_work;
struct delayed_work delete_work;
int dup_ret;
};
@@ -105,29 +104,6 @@ static void kmod_dup_request_delete(struct work_struct *work)
put_kmod_req(kmod_req);
}
-static void kmod_dup_request_complete(struct work_struct *work)
-{
- struct kmod_dup_req *kmod_req;
-
- kmod_req = container_of(work, struct kmod_dup_req, complete_work);
-
- /*
- * This will ensure that the kernel will let all the waiters get
- * informed its time to check the return value. It's time to
- * go home.
- */
- complete_all(&kmod_req->first_req_done);
-
- /*
- * Now that we have allowed prior request_module() calls to go on
- * with life, let's schedule deleting this entry. We don't have
- * to do it right away, but we *eventually* want to do it so to not
- * let this linger forever as this is just a boot optimization for
- * possible abuses of vmalloc() incurred by finit_module() thrashing.
- */
- queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ);
-}
-
bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
{
struct kmod_dup_req *kmod_req, *new_kmod_req;
@@ -143,7 +119,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
refcount_set(&new_kmod_req->refcount, 1);
memcpy(new_kmod_req->name, module_name, strlen(module_name));
- INIT_WORK(&new_kmod_req->complete_work, kmod_dup_request_complete);
INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
init_completion(&new_kmod_req->first_req_done);
@@ -246,23 +221,22 @@ void kmod_dup_request_announce(char *module_name, int ret)
* that is owned by the current task.
*/
kmod_req = kmod_dup_request_lookup(module_name);
+ mutex_unlock(&kmod_dup_mutex);
+
if (!kmod_req)
- goto out;
+ return;
kmod_req->dup_ret = ret;
+ /* Inform all the duplicate waiters to check the return value. */
+ complete_all(&kmod_req->first_req_done);
+
/*
- * If we complete() here we may allow duplicate threads
- * to continue before the first one that submitted the
- * request. We're in no rush also, given that each and
- * every bounce back to userspace is slow we avoid that
- * with a slight delay here. So queueue up the completion
- * and let duplicates suffer, just wait a tad bit longer.
- * There is no rush. But we also don't want to hold the
- * caller up forever or introduce any boot delays.
+ * Now that we have allowed prior request_module() calls to go on
+ * with life, let's schedule deleting this entry. We don't have
+ * to do it right away, but we *eventually* want to do it so to not
+ * let this linger forever as this is just a boot optimization for
+ * possible abuses of vmalloc() incurred by finit_module() thrashing.
*/
- queue_work(system_dfl_wq, &kmod_req->complete_work);
-
-out:
- mutex_unlock(&kmod_dup_mutex);
+ queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
2026-07-20 12:15 ` [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling Petr Pavlu
2026-07-20 12:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
@ 2026-07-20 12:15 ` Petr Pavlu
2026-07-20 12:25 ` sashiko-bot
2026-07-20 12:15 ` [PATCH 4/5] module/dups: Use strcmp() to compare module names Petr Pavlu
2026-07-20 12:15 ` [PATCH 5/5] module/dups: Clean up includes Petr Pavlu
4 siblings, 1 reply; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
Using scoped guards for kmod_dup_mutex shortens the code and clarifies
where the lock is taken in kmod_dup_request_exists_wait().
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
kernel/module/dups.c | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index f0bdd7560d7e..a62619ec057e 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -97,9 +97,8 @@ static void kmod_dup_request_delete(struct work_struct *work)
* kmod. The inneficies there are a call to modprobe and modprobe
* just returning 0.
*/
- mutex_lock(&kmod_dup_mutex);
- list_del(&kmod_req->list);
- mutex_unlock(&kmod_dup_mutex);
+ scoped_guard(mutex, &kmod_dup_mutex)
+ list_del(&kmod_req->list);
put_kmod_req(kmod_req);
}
@@ -122,10 +121,13 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
init_completion(&new_kmod_req->first_req_done);
- mutex_lock(&kmod_dup_mutex);
+ scoped_guard(mutex, &kmod_dup_mutex) {
+ kmod_req = kmod_dup_request_lookup(module_name);
+ if (kmod_req) {
+ get_kmod_req(kmod_req);
+ goto dup_req;
+ }
- kmod_req = kmod_dup_request_lookup(module_name);
- if (!kmod_req) {
/*
* If the first request that came through for a module
* was with request_module_nowait() we cannot wait for it
@@ -140,7 +142,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
if (!wait) {
kfree(new_kmod_req);
pr_debug("New request_module_nowait() for %s -- cannot track duplicates for this request\n", module_name);
- mutex_unlock(&kmod_dup_mutex);
return false;
}
@@ -150,13 +151,10 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
*/
pr_debug("New request_module() for %s\n", module_name);
list_add(&new_kmod_req->list, &dup_kmod_reqs);
- mutex_unlock(&kmod_dup_mutex);
return false;
}
- get_kmod_req(kmod_req);
- mutex_unlock(&kmod_dup_mutex);
-
+dup_req:
/* We are dealing with a duplicate request now */
kfree(new_kmod_req);
@@ -214,14 +212,12 @@ void kmod_dup_request_announce(char *module_name, int ret)
{
struct kmod_dup_req *kmod_req;
- mutex_lock(&kmod_dup_mutex);
-
/*
* Find the entry previously added in kmod_dup_request_exists_wait()
* that is owned by the current task.
*/
- kmod_req = kmod_dup_request_lookup(module_name);
- mutex_unlock(&kmod_dup_mutex);
+ scoped_guard(mutex, &kmod_dup_mutex)
+ kmod_req = kmod_dup_request_lookup(module_name);
if (!kmod_req)
return;
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 4/5] module/dups: Use strcmp() to compare module names
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
` (2 preceding siblings ...)
2026-07-20 12:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex Petr Pavlu
@ 2026-07-20 12:15 ` Petr Pavlu
2026-07-20 12:15 ` [PATCH 5/5] module/dups: Clean up includes Petr Pavlu
4 siblings, 0 replies; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
Use strcmp() instead of strlen()+memcmp() to compare module names in
kmod_dup_request_lookup(), since all strings are NUL-terminated.
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
kernel/module/dups.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index a62619ec057e..43cec4cec4fd 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -70,11 +70,9 @@ static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name)
lockdep_assert_held(&kmod_dup_mutex);
list_for_each_entry(kmod_req, &dup_kmod_reqs, list) {
- if (strlen(kmod_req->name) == strlen(module_name) &&
- !memcmp(kmod_req->name, module_name, strlen(module_name))) {
+ if (!strcmp(kmod_req->name, module_name))
return kmod_req;
- }
- }
+ }
return NULL;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 5/5] module/dups: Clean up includes
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
` (3 preceding siblings ...)
2026-07-20 12:15 ` [PATCH 4/5] module/dups: Use strcmp() to compare module names Petr Pavlu
@ 2026-07-20 12:15 ` Petr Pavlu
2026-07-20 12:32 ` sashiko-bot
4 siblings, 1 reply; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 12:15 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, linux-modules, linux-kernel
The kernel/module/dups.c file relies on the following definitions and
associated functions:
* module_param() -> linux/moduleparam.h,
* DEFINE_MUTEX() -> linux/mutex.h,
* LIST_HEAD(), list_for_each_entry(), ... -> linux/list.h,
* refcount_t, refcount_inc(), ... -> linux/refcount.h,
* MODULE_NAME_LEN -> linux/module.h,
* completion, complete_all(), ... -> linux/completion.h,
* delayed_work, work_struct, ... -> linux/workqueue.h,
* lockdep_assert_held() -> linux/lockdep.h,
* strcmp(), memcpy() -> linux/string.h,
* container_of() -> linux/container_of.h,
* scoped_guard() -> linux/cleanup.h,
* kzalloc_obj(), kfree() -> linux/slab.h,
* pr_debug(), pr_warn() -> linux/printk.h,
* WARN() -> linux/bug.h,
* TASK_KILLABLE -> linux/sched.h,
* HZ -> linux/param.h.
Update the file's include list accordingly.
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
kernel/module/dups.c | 32 ++++++++++++--------------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index 43cec4cec4fd..76faa662f14d 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -7,30 +7,22 @@
#define pr_fmt(fmt) "module: " fmt
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/param.h>
+#include <linux/printk.h>
+#include <linux/refcount.h>
#include <linux/sched.h>
-#include <linux/sched/task.h>
-#include <linux/binfmts.h>
-#include <linux/syscalls.h>
-#include <linux/unistd.h>
-#include <linux/kmod.h>
#include <linux/slab.h>
-#include <linux/completion.h>
-#include <linux/cred.h>
-#include <linux/file.h>
+#include <linux/string.h>
#include <linux/workqueue.h>
-#include <linux/security.h>
-#include <linux/mount.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/resource.h>
-#include <linux/notifier.h>
-#include <linux/suspend.h>
-#include <linux/rwsem.h>
-#include <linux/ptrace.h>
-#include <linux/async.h>
-#include <linux/uaccess.h>
-#include <linux/refcount.h>
#include "internal.h"
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex
2026-07-20 12:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex Petr Pavlu
@ 2026-07-20 12:25 ` sashiko-bot
2026-07-20 13:28 ` Petr Pavlu
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-07-20 12:25 UTC (permalink / raw)
To: Petr Pavlu; +Cc: linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Mixing goto-based cleanup labels with scope-based cleanup (scoped_guard) in the same function.
--
--- Patch [3]: [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex ---
commit 3299ca2ac8ae46392f412ae7c186f4907e7f8201
Author: Petr Pavlu <petr.pavlu@suse.com>
module/dups: Use scoped guards for kmod_dup_mutex
This patch replaces explicit mutex_lock/unlock calls for kmod_dup_mutex
with scoped_guard to shorten the code and clarify the lock's scope.
> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
> index f0bdd7560d7e7..a62619ec057ed 100644
> --- a/kernel/module/dups.c
> +++ b/kernel/module/dups.c
[ ... ]
> @@ -122,10 +121,13 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
> INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
> init_completion(&new_kmod_req->first_req_done);
>
> - mutex_lock(&kmod_dup_mutex);
> + scoped_guard(mutex, &kmod_dup_mutex) {
> + kmod_req = kmod_dup_request_lookup(module_name);
> + if (kmod_req) {
> + get_kmod_req(kmod_req);
> + goto dup_req;
> + }
>
> - kmod_req = kmod_dup_request_lookup(module_name);
> - if (!kmod_req) {
[Severity: Low]
Does this mix scope-based cleanup with goto-based cleanup in the same
function?
Looking further down in kmod_dup_request_exists_wait(), there are still
several goto statements that jump to an out label for manual cleanup:
if (!wait) {
/* ... */
*dup_ret = 0;
goto out;
}
ret = wait_for_completion_state(&kmod_req->first_req_done,
TASK_KILLABLE);
if (ret) {
*dup_ret = ret;
goto out;
}
*dup_ret = kmod_req->dup_ret;
out:
put_kmod_req(kmod_req);
return true;
According to include/linux/cleanup.h, the expectation is that usage of
"goto" and cleanup helpers is never mixed in the same function. All resources
that need a "goto" cleanup should either be converted to scope-based cleanup,
or none of them should.
Should put_kmod_req() be converted to use scope-based cleanup as well, or
should this function avoid using scoped_guard() to keep the cleanup pattern
consistent?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720121621.750661-1-petr.pavlu@suse.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/5] module/dups: Inform duplicate requests about the result directly
2026-07-20 12:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
@ 2026-07-20 12:28 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-20 12:28 UTC (permalink / raw)
To: Petr Pavlu; +Cc: linux-modules
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Dropping `kmod_dup_mutex` prematurely without acquiring a reference leads to a Use-After-Free and data races.
Pre-existing issues:
- [High] A `request_module_nowait` call can erroneously complete a concurrent `request_module` duplicate tracking object prematurely.
--
--- Patch [2]: [PATCH 2/5] module/dups: Inform duplicate requests about the result directly ---
commit 5b4a732b8aa814f2e4a78fd3e59bb299a50f8a41
Author: Petr Pavlu <petr.pavlu@suse.com>
module/dups: Inform duplicate requests about the result directly
When kmod_dup_request_announce() announces the completion of
a request_module() call to duplicate waiters, it queues a work item to
invoke kmod_dup_request_complete(), and only that function calls
complete_all().
This adds an arbitrary delay that is unnecessary and provides little
benefit. Call complete_all() directly from kmod_dup_request_announce()
instead.
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
> index e1828e865edde..f0bdd7560d7e7 100644
> --- a/kernel/module/dups.c
> +++ b/kernel/module/dups.c
[ ... ]
> @@ -246,23 +221,22 @@ void kmod_dup_request_announce(char *module_name, int ret)
> * that is owned by the current task.
> */
> kmod_req = kmod_dup_request_lookup(module_name);
> + mutex_unlock(&kmod_dup_mutex);
> +
> if (!kmod_req)
> - goto out;
> + return;
>
> kmod_req->dup_ret = ret;
[Severity: High]
Does unlocking kmod_dup_mutex here leave kmod_req unprotected?
If a thread is preempted after unlocking the mutex but before writing to
kmod_req->dup_ret, could delete_work execute, drop the refcount to zero,
and free kmod_req? This would result in a use-after-free when the preempted
thread resumes and attempts to write to kmod_req->dup_ret or call
complete_all(). Should we hold the mutex longer or acquire a reference?
> + /* Inform all the duplicate waiters to check the return value. */
> + complete_all(&kmod_req->first_req_done);
[Severity: High]
This is a pre-existing issue, but can this prematurely complete a concurrent
request_module() call?
When request_module_nowait() is called, it skips adding a tracking object to
the dup_kmod_reqs list, but continues to execute call_modprobe() and
unconditionally calls kmod_dup_request_announce().
If a concurrent request_module() call had created a tracking object for the
same module, the nowait thread's lookup will find it and call complete_all(),
prematurely waking up all duplicate waiters before the original modprobe has
actually finished. Does this function need to verify the caller owns the
request before completing it?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720121621.750661-1-petr.pavlu@suse.com?part=2
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 5/5] module/dups: Clean up includes
2026-07-20 12:15 ` [PATCH 5/5] module/dups: Clean up includes Petr Pavlu
@ 2026-07-20 12:32 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-20 12:32 UTC (permalink / raw)
To: Petr Pavlu; +Cc: linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The function `kmod_dup_request_exists_wait()` mixes `goto`-based error/cleanup handling with `scoped_guard()` declarations, violating subsystem guidelines.
--
--- Patch [5]: [PATCH 5/5] module/dups: Clean up includes ---
commit 94d1ce7350f8ea78793315796cdc680daf4e1298
Author: Petr Pavlu <petr.pavlu@suse.com>
module/dups: Clean up includes
This patch cleans up the #include directives in kernel/module/dups.c.
It explicitly includes the headers for all definitions and functions
relied upon by the file.
> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
> index 43cec4cec4fdb..76faa662f14d8 100644
> --- a/kernel/module/dups.c
> +++ b/kernel/module/dups.c
> @@ -7,30 +7,22 @@
>
> #define pr_fmt(fmt) "module: " fmt
>
> +#include <linux/bug.h>
> +#include <linux/cleanup.h>
[Severity: Low]
Does kmod_dup_request_exists_wait() violate the cleanup subsystem guidelines
by mixing goto labels and scoped_guard() declarations?
kernel/module/dups.c:kmod_dup_request_exists_wait() {
...
scoped_guard(mutex, &kmod_dup_mutex) {
kmod_req = kmod_dup_request_lookup(module_name);
if (kmod_req) {
get_kmod_req(kmod_req);
goto dup_req;
}
...
}
...
dup_req:
...
if (!wait) {
...
goto out;
}
...
out:
put_kmod_req(kmod_req);
return true;
}
The cleanup subsystem guidelines expect that usage of goto and cleanup
helpers are never mixed in the same function.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720121621.750661-1-petr.pavlu@suse.com?part=5
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling
2026-07-20 12:15 ` [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling Petr Pavlu
@ 2026-07-20 12:32 ` sashiko-bot
2026-07-20 13:28 ` Petr Pavlu
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-07-20 12:32 UTC (permalink / raw)
To: Petr Pavlu; +Cc: linux-modules
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] A pre-existing use-after-free exists in `kmod_dup_request_complete()` due to `kmod_dup_request_announce()` queuing `complete_work` without holding a reference.
--
--- Patch [1]: [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling ---
commit c16274209ebc300f334c8ae6c8a5bec3494aa562
Author: Petr Pavlu <petr.pavlu@suse.com>
module/dups: Fix use-after-free in kmod_dup_req lifetime handling
The kmod dups code uses RCU to ensure that a kmod_dup_req instance is freed
only after it is no longer referenced. When releasing an instance, the
kmod_dup_request_delete() function removes the kmod_dup_req from the
dup_kmod_reqs list, waits via synchronize_rcu() and finally frees it.
However, this doesn't work correctly because parallel users referencing the
instance in kmod_dup_request_exists_wait() don't enter an RCU read-side
critical section. This can result in a use-after-free.
Fix the issue by changing the lifecycle management of kmod_dup_req to use
reference counting.
> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
> index 1d720a5311ba0..e1828e865edde 100644
> --- a/kernel/module/dups.c
> +++ b/kernel/module/dups.c
[ ... ]
> @@ -224,6 +241,10 @@ void kmod_dup_request_announce(char *module_name, int ret)
>
> mutex_lock(&kmod_dup_mutex);
>
> + /*
> + * Find the entry previously added in kmod_dup_request_exists_wait()
> + * that is owned by the current task.
> + */
> kmod_req = kmod_dup_request_lookup(module_name);
> if (!kmod_req)
> goto out;
[Severity: Critical]
This is a pre-existing issue, but is there still a use-after-free risk for
complete_work even with the refcounting switch?
Looking further down in kmod_dup_request_announce(), it queues the
asynchronous work item without incrementing the object's reference count:
queue_work(system_dfl_wq, &kmod_req->complete_work);
Because no reference is held for the work item, kmod_req could be freed by
delete_work before kmod_dup_request_complete() finishes executing.
When the work executes in kmod_dup_request_complete() and dereferences
kmod_req to call complete_all(&kmod_req->first_req_done), this could access
freed memory.
Can concurrent auto-loads trigger this? For instance, if multiple tasks
execute __request_module() concurrently:
Task 1 (wait=false) Task 2 (wait=true)
kmod_dup_request_announce()
queue_work(complete_work)
kmod_dup_request_announce()
queue_work(complete_work)
Could the second complete_work execution be queued just before the 60-second
timer expires, and run right after delete_work frees the object?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720121621.750661-1-petr.pavlu@suse.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling
2026-07-20 12:32 ` sashiko-bot
@ 2026-07-20 13:28 ` Petr Pavlu
0 siblings, 0 replies; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 13:28 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-modules
On 7/20/26 2:32 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [Critical] A pre-existing use-after-free exists in `kmod_dup_request_complete()` due to `kmod_dup_request_announce()` queuing `complete_work` without holding a reference.
> --
>
> --- Patch [1]: [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling ---
> commit c16274209ebc300f334c8ae6c8a5bec3494aa562
> Author: Petr Pavlu <petr.pavlu@suse.com>
>
> module/dups: Fix use-after-free in kmod_dup_req lifetime handling
>
> The kmod dups code uses RCU to ensure that a kmod_dup_req instance is freed
> only after it is no longer referenced. When releasing an instance, the
> kmod_dup_request_delete() function removes the kmod_dup_req from the
> dup_kmod_reqs list, waits via synchronize_rcu() and finally frees it.
> However, this doesn't work correctly because parallel users referencing the
> instance in kmod_dup_request_exists_wait() don't enter an RCU read-side
> critical section. This can result in a use-after-free.
>
> Fix the issue by changing the lifecycle management of kmod_dup_req to use
> reference counting.
>
>> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
>> index 1d720a5311ba0..e1828e865edde 100644
>> --- a/kernel/module/dups.c
>> +++ b/kernel/module/dups.c
>
> [ ... ]
>
>> @@ -224,6 +241,10 @@ void kmod_dup_request_announce(char *module_name, int ret)
>>
>> mutex_lock(&kmod_dup_mutex);
>>
>> + /*
>> + * Find the entry previously added in kmod_dup_request_exists_wait()
>> + * that is owned by the current task.
>> + */
>> kmod_req = kmod_dup_request_lookup(module_name);
>> if (!kmod_req)
>> goto out;
>
> [Severity: Critical]
> This is a pre-existing issue, but is there still a use-after-free risk for
> complete_work even with the refcounting switch?
>
> Looking further down in kmod_dup_request_announce(), it queues the
> asynchronous work item without incrementing the object's reference count:
>
> queue_work(system_dfl_wq, &kmod_req->complete_work);
>
> Because no reference is held for the work item, kmod_req could be freed by
> delete_work before kmod_dup_request_complete() finishes executing.
>
> When the work executes in kmod_dup_request_complete() and dereferences
> kmod_req to call complete_all(&kmod_req->first_req_done), this could access
> freed memory.
>
> Can concurrent auto-loads trigger this? For instance, if multiple tasks
> execute __request_module() concurrently:
>
> Task 1 (wait=false) Task 2 (wait=true)
> kmod_dup_request_announce()
> queue_work(complete_work)
> kmod_dup_request_announce()
> queue_work(complete_work)
>
> Could the second complete_work execution be queued just before the 60-second
> timer expires, and run right after delete_work frees the object?
>
I agree. The code has an issue when two concurrent request_module() are
active, one using wait=true and the other wait=false. When the
wait=false call wins and announces the result via
kmod_dup_request_announce(), it can complete the kmod_dup_req object
allocated by the wait=true call, which can later result in
a use-after-free.
Sashiko also reported the same issue in a reply to patch #2.
-- Petr
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex
2026-07-20 12:25 ` sashiko-bot
@ 2026-07-20 13:28 ` Petr Pavlu
0 siblings, 0 replies; 12+ messages in thread
From: Petr Pavlu @ 2026-07-20 13:28 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-modules
On 7/20/26 2:25 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Mixing goto-based cleanup labels with scope-based cleanup (scoped_guard) in the same function.
> --
>
> --- Patch [3]: [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex ---
> commit 3299ca2ac8ae46392f412ae7c186f4907e7f8201
> Author: Petr Pavlu <petr.pavlu@suse.com>
>
> module/dups: Use scoped guards for kmod_dup_mutex
>
> This patch replaces explicit mutex_lock/unlock calls for kmod_dup_mutex
> with scoped_guard to shorten the code and clarify the lock's scope.
>
>> diff --git a/kernel/module/dups.c b/kernel/module/dups.c
>> index f0bdd7560d7e7..a62619ec057ed 100644
>> --- a/kernel/module/dups.c
>> +++ b/kernel/module/dups.c
>
> [ ... ]
>
>> @@ -122,10 +121,13 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
>> INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete);
>> init_completion(&new_kmod_req->first_req_done);
>>
>> - mutex_lock(&kmod_dup_mutex);
>> + scoped_guard(mutex, &kmod_dup_mutex) {
>> + kmod_req = kmod_dup_request_lookup(module_name);
>> + if (kmod_req) {
>> + get_kmod_req(kmod_req);
>> + goto dup_req;
>> + }
>>
>> - kmod_req = kmod_dup_request_lookup(module_name);
>> - if (!kmod_req) {
>
> [Severity: Low]
> Does this mix scope-based cleanup with goto-based cleanup in the same
> function?
>
> Looking further down in kmod_dup_request_exists_wait(), there are still
> several goto statements that jump to an out label for manual cleanup:
>
> if (!wait) {
> /* ... */
> *dup_ret = 0;
> goto out;
> }
>
> ret = wait_for_completion_state(&kmod_req->first_req_done,
> TASK_KILLABLE);
> if (ret) {
> *dup_ret = ret;
> goto out;
> }
>
> *dup_ret = kmod_req->dup_ret;
>
> out:
> put_kmod_req(kmod_req);
> return true;
>
> According to include/linux/cleanup.h, the expectation is that usage of
> "goto" and cleanup helpers is never mixed in the same function. All resources
> that need a "goto" cleanup should either be converted to scope-based cleanup,
> or none of them should.
>
> Should put_kmod_req() be converted to use scope-based cleanup as well, or
> should this function avoid using scoped_guard() to keep the cleanup pattern
> consistent?
>
Fair, I'll clean this up.
Sashiko also reported the same issue in a reply to patch #5.
-- Petr
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-07-20 13:28 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 12:15 [PATCH 0/5] module/dups: Fix a use-after-free and improve the code Petr Pavlu
2026-07-20 12:15 ` [PATCH 1/5] module/dups: Fix use-after-free in kmod_dup_req lifetime handling Petr Pavlu
2026-07-20 12:32 ` sashiko-bot
2026-07-20 13:28 ` Petr Pavlu
2026-07-20 12:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
2026-07-20 12:28 ` sashiko-bot
2026-07-20 12:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex Petr Pavlu
2026-07-20 12:25 ` sashiko-bot
2026-07-20 13:28 ` Petr Pavlu
2026-07-20 12:15 ` [PATCH 4/5] module/dups: Use strcmp() to compare module names Petr Pavlu
2026-07-20 12:15 ` [PATCH 5/5] module/dups: Clean up includes Petr Pavlu
2026-07-20 12:32 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.