All of lore.kernel.org
 help / color / mirror / Atom feed
* [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.