mirror of https://lore.kernel.org/lkml/
 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; 6+ 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] 6+ 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:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ 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	[flat|nested] 6+ 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:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex Petr Pavlu
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ 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	[flat|nested] 6+ 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: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, 0 replies; 6+ 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	[flat|nested] 6+ 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; 6+ 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	[flat|nested] 6+ 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
  4 siblings, 0 replies; 6+ 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	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-07-20 12:16 UTC | newest]

Thread overview: 6+ 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:15 ` [PATCH 2/5] module/dups: Inform duplicate requests about the result directly Petr Pavlu
2026-07-20 12:15 ` [PATCH 3/5] module/dups: Use scoped guards for kmod_dup_mutex 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®