Petr Pavlu 63bdff
From: Petr Pavlu <petr.pavlu@suse.com>
Petr Pavlu 63bdff
Date: Mon, 5 Dec 2022 11:35:57 +0100
Petr Pavlu 63bdff
Subject: module: Don't wait for GOING modules
Petr Pavlu 63bdff
Git-commit: 0254127ab977e70798707a7a2b757c9f3c971210
Petr Pavlu 63bdff
Patch-mainline: v6.2-rc6
Petr Pavlu 63bdff
References: bsc#1196058, bsc#1186449, bsc#1204356, bsc#1204662
Petr Pavlu 63bdff
Petr Pavlu 63bdff
During a system boot, it can happen that the kernel receives a burst of
Petr Pavlu 63bdff
requests to insert the same module but loading it eventually fails
Petr Pavlu 63bdff
during its init call. For instance, udev can make a request to insert
Petr Pavlu 63bdff
a frequency module for each individual CPU when another frequency module
Petr Pavlu 63bdff
is already loaded which causes the init function of the new module to
Petr Pavlu 63bdff
return an error.
Petr Pavlu 63bdff
Petr Pavlu 63bdff
Since commit 6e6de3dee51a ("kernel/module.c: Only return -EEXIST for
Petr Pavlu 63bdff
modules that have finished loading"), the kernel waits for modules in
Petr Pavlu 63bdff
MODULE_STATE_GOING state to finish unloading before making another
Petr Pavlu 63bdff
attempt to load the same module.
Petr Pavlu 63bdff
Petr Pavlu 63bdff
This creates unnecessary work in the described scenario and delays the
Petr Pavlu 63bdff
boot. In the worst case, it can prevent udev from loading drivers for
Petr Pavlu 63bdff
other devices and might cause timeouts of services waiting on them and
Petr Pavlu 63bdff
subsequently a failed boot.
Petr Pavlu 63bdff
Petr Pavlu 63bdff
This patch attempts a different solution for the problem 6e6de3dee51a
Petr Pavlu 63bdff
was trying to solve. Rather than waiting for the unloading to complete,
Petr Pavlu 63bdff
it returns a different error code (-EBUSY) for modules in the GOING
Petr Pavlu 63bdff
state. This should avoid the error situation that was described in
Petr Pavlu 63bdff
6e6de3dee51a (user space attempting to load a dependent module because
Petr Pavlu 63bdff
the -EEXIST error code would suggest to user space that the first module
Petr Pavlu 63bdff
had been loaded successfully), while avoiding the delay situation too.
Petr Pavlu 63bdff
Petr Pavlu 63bdff
This has been tested on linux-next since December 2022 and passes
Petr Pavlu 63bdff
all kmod selftests except test 0009 with module compression enabled
Petr Pavlu 63bdff
but it has been confirmed that this issue has existed and has gone
Petr Pavlu 63bdff
unnoticed since prior to this commit and can also be reproduced without
Petr Pavlu 63bdff
module compression with a simple usleep(5000000) on tools/modprobe.c [0].
Petr Pavlu 63bdff
These failures are caused by hitting the kernel mod_concurrent_max and can
Petr Pavlu 63bdff
happen either due to a self inflicted kernel module auto-loead DoS somehow
Petr Pavlu 63bdff
or on a system with large CPU count and each CPU count incorrectly triggering
Petr Pavlu 63bdff
many module auto-loads. Both of those issues need to be fixed in-kernel.
Petr Pavlu 63bdff
Petr Pavlu 63bdff
[0] https://lore.kernel.org/all/Y9A4fiobL6IHp%2F%2FP@bombadil.infradead.org/
Petr Pavlu 63bdff
Petr Pavlu 63bdff
Fixes: 6e6de3dee51a ("kernel/module.c: Only return -EEXIST for modules that have finished loading")
Petr Pavlu 63bdff
Co-developed-by: Martin Wilck <mwilck@suse.com>
Petr Pavlu 63bdff
Signed-off-by: Martin Wilck <mwilck@suse.com>
Petr Pavlu 63bdff
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
Petr Pavlu 63bdff
Cc: stable@vger.kernel.org
Petr Pavlu 63bdff
Reviewed-by: Petr Mladek <pmladek@suse.com>
Petr Pavlu 63bdff
[mcgrof: enhance commit log with testing and kmod test result interpretation ]
Petr Pavlu 63bdff
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
Petr Pavlu 63bdff
---
Petr Pavlu 63bdff
 kernel/module.c | 26 +++++++++++++++++++++-----
Petr Pavlu 63bdff
 1 file changed, 21 insertions(+), 5 deletions(-)
Petr Pavlu 63bdff
Petr Pavlu 63bdff
diff --git a/kernel/module.c b/kernel/module.c
Petr Pavlu 63bdff
index 48568a0f5651..4ac3fe43e6c8 100644
Petr Pavlu 63bdff
--- a/kernel/module.c
Petr Pavlu 63bdff
+++ b/kernel/module.c
Petr Pavlu 63bdff
@@ -2393,7 +2393,8 @@ static bool finished_loading(const char *name)
Petr Pavlu 63bdff
 	sched_annotate_sleep();
Petr Pavlu 63bdff
 	mutex_lock(&module_mutex);
Petr Pavlu 63bdff
 	mod = find_module_all(name, strlen(name), true);
Petr Pavlu 63bdff
-	ret = !mod || mod->state == MODULE_STATE_LIVE;
Petr Pavlu 63bdff
+	ret = !mod || mod->state == MODULE_STATE_LIVE
Petr Pavlu 63bdff
+		|| mod->state == MODULE_STATE_GOING;
Petr Pavlu 63bdff
 	mutex_unlock(&module_mutex);
Petr Pavlu 63bdff
 
Petr Pavlu 63bdff
 	return ret;
Petr Pavlu 63bdff
@@ -2569,20 +2570,35 @@ static int add_unformed_module(struct module *mod)
Petr Pavlu 63bdff
 
Petr Pavlu 63bdff
 	mod->state = MODULE_STATE_UNFORMED;
Petr Pavlu 63bdff
 
Petr Pavlu 63bdff
-again:
Petr Pavlu 63bdff
 	mutex_lock(&module_mutex);
Petr Pavlu 63bdff
 	old = find_module_all(mod->name, strlen(mod->name), true);
Petr Pavlu 63bdff
 	if (old != NULL) {
Petr Pavlu 63bdff
-		if (old->state != MODULE_STATE_LIVE) {
Petr Pavlu 63bdff
+		if (old->state == MODULE_STATE_COMING
Petr Pavlu 63bdff
+		    || old->state == MODULE_STATE_UNFORMED) {
Petr Pavlu 63bdff
 			/* Wait in case it fails to load. */
Petr Pavlu 63bdff
 			mutex_unlock(&module_mutex);
Petr Pavlu 63bdff
 			err = wait_event_interruptible(module_wq,
Petr Pavlu 63bdff
 					       finished_loading(mod->name));
Petr Pavlu 63bdff
 			if (err)
Petr Pavlu 63bdff
 				goto out_unlocked;
Petr Pavlu 63bdff
-			goto again;
Petr Pavlu 63bdff
+
Petr Pavlu 63bdff
+			/* The module might have gone in the meantime. */
Petr Pavlu 63bdff
+			mutex_lock(&module_mutex);
Petr Pavlu 63bdff
+			old = find_module_all(mod->name, strlen(mod->name),
Petr Pavlu 63bdff
+					      true);
Petr Pavlu 63bdff
 		}
Petr Pavlu 63bdff
-		err = -EEXIST;
Petr Pavlu 63bdff
+
Petr Pavlu 63bdff
+		/*
Petr Pavlu 63bdff
+		 * We are here only when the same module was being loaded. Do
Petr Pavlu 63bdff
+		 * not try to load it again right now. It prevents long delays
Petr Pavlu 63bdff
+		 * caused by serialized module load failures. It might happen
Petr Pavlu 63bdff
+		 * when more devices of the same type trigger load of
Petr Pavlu 63bdff
+		 * a particular module.
Petr Pavlu 63bdff
+		 */
Petr Pavlu 63bdff
+		if (old && old->state == MODULE_STATE_LIVE)
Petr Pavlu 63bdff
+			err = -EEXIST;
Petr Pavlu 63bdff
+		else
Petr Pavlu 63bdff
+			err = -EBUSY;
Petr Pavlu 63bdff
 		goto out;
Petr Pavlu 63bdff
 	}
Petr Pavlu 63bdff
 	mod_update_bounds(mod);
Petr Pavlu 63bdff