aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuis R. Rodriguez <mcgrof@kernel.org>2016-11-11 10:35:35 -0800
committerLuis R. Rodriguez <mcgrof@kernel.org>2016-12-08 10:40:48 -0800
commit4f0003e2971caf19a29381abfb68d65fd670507b (patch)
tree5f1cec562d19069ab157bcee9d2c7000145e5432
parentadeee381470c0abd89f4789aa0a51a0942ade37a (diff)
downloadlinux-20161208-kmod-test-driver.tar.gz
kmod: add a sanity check on module loading20161208-kmod-test-driver
kmod has an optimization in place whereby if a some kernel code uses request_module() on a module already loaded we never bother userspace as the module already is loaded. This is not true for get_fs_type() though as it uses aliases. Additionally kmod <= v19 was broken -- it returns 0 to modprobe calls, assuming the kernel module is built-in, where really we have a race as the module starts forming. kmod <= v19 has incorrect userspace heuristics, a userspace kmod fix is available for it: http://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/libkmod/libkmod-module.c?id=fd44a98ae2eb5eb32161088954ab21e58e19dfc4 This changes kmod to address both: o Provides the alias optimization for get_fs_type() so modules already loaded do not get re-requested. o Provides a sanity test to verify modprobe's work This is important given how any get_fs_type() users assert success means we're ready to go, and tests with the new test_kmod stress driver reveal that request_module() and get_fs_type() might fail for a few other reasons. You don't need old kmod to fail on request_module() or get_fs_type(), with the right system setup, these calls *can* fail today. Although this does get us in the business of keeping alias maps in kernel, the the work to support and maintain this is trivial. Aditionally, since it may be important get_fs_type() should not fail on certain systems, this tightens things up a bit more. The TL;DR: kmod <= v19 will return 0 on modprobe calls if you are built-in, however its heuristics for checking if you are built-in were broken. It assumed that having the directory /sys/module/module-name but not having the file /sys/module/module-name/initstate is sufficient to assume a module is built-in. The kernel loads the inittstate attribute *after* it creates the directory. This is an issue when modprobe returns 0 for kernel calls which assumes a return of 0 on request_module() can give you the right to assert the module is loaded and live. We cannot trust returns of modprobe as 0 in the kernel, we need to verify that modules are live if modprobe return 0 but only if modules *are* modules. The kernel heuristic we use to determine if a module is built-in is that if modprobe returns 0 we know we must be built-in or a module, but if we are a module clearly we must have a lingering kmod dangling on our linked list. If there is no modules there we are *somewhat* certain the module must be built in. This is not enough though... we cannot easily work around this since the kernel can use aliases to userspace for modules calls. For instance fs/namespace.c uses fs-modulename for filesystesms on get_fs_type(), so these need to be taken into consideration as well. Using kmod <= 19 will give you a NULL get_fs_type() return even though the module was loaded... That is a corner case, there are other failures for request_module() though -- the other failures are not easy to reproduce though but fortunately we have a stress test driver to help with that now. Use the following tests: # tools/testing/selftests/kmod/kmod.sh -t 0008 # tools/testing/selftests/kmod/kmod.sh -t 0009 You can more easily see this error if you have kmod <= v19 installed. You will need to install kmod <= v19, be sure to install its modprobe into /sbin/ as by default the 'make install' target does not replace your own. This test helps cure test_kmod cases 0008 0009 so enable them. Reported-by: Martin Wilck <martin.wilck@suse.com> Reported-by: Randy Wright <rwright@hpe.com> Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
-rw-r--r--kernel/kmod.c73
-rw-r--r--kernel/module.c11
2 files changed, 81 insertions, 3 deletions
diff --git a/kernel/kmod.c b/kernel/kmod.c
index a0f449f77ed73c..6bf0feab41d105 100644
--- a/kernel/kmod.c
+++ b/kernel/kmod.c
@@ -61,6 +61,11 @@ static DECLARE_RWSEM(umhelper_sem);
#ifdef CONFIG_MODULES
+bool finished_loading(const char *name);
+int module_wait_until_finished(const char *name);
+struct module *find_module_all(const char *name, size_t len,
+ bool even_unformed);
+
/*
modprobe_path is set via /proc/sys.
*/
@@ -158,6 +163,72 @@ int get_kmod_umh_count(void)
return atomic_read(&kmod_concurrent);
}
+static bool kmod_exists(char *name)
+{
+ struct module *mod;
+
+ mutex_lock(&module_mutex);
+ mod = find_module_all(name, strlen(name), true);
+ mutex_unlock(&module_mutex);
+
+ if (mod)
+ return true;
+
+ return false;
+}
+
+/*
+ * The assumption is this must be a module, it could still not be live though
+ * since kmod <= 19 returns 0 even if it was not ready yet. Allow for force
+ * wait check in case you are stuck on old userspace.
+ */
+static int wait_for_kmod(char *name)
+{
+ int ret = 0;
+
+ if (!finished_loading(name))
+ ret = module_wait_until_finished(name);
+
+ return ret;
+}
+
+/*
+ * kmod <= 19 will tell us modprobe returned 0 even if the module
+ * is not ready yet, it does this because it checks the /sys/module/mod-name
+ * directory and if its created but the /sys/module/mod-name/initstate is not
+ * created it assumes you have a built-in driver. At this point the module
+ * is still unformed, and telling the kernel at any point via request_module()
+ * will cause issues given a lot of places in the kernel assert that the driver
+ * will be present and ready. We need to account for this.
+ *
+ * If we had a module and even if buggy modprobe returned 0, we know we'd at
+ * least have a dangling kmod entry we could fetch.
+ *
+ * If modprobe returned 0 and we cannot find a kmod entry this is a good
+ * indicator your by userspace and kernel space that what you have is built-in.
+ *
+ * If modprobe returned 0 and we can find a kmod entry we should air on the
+ * side of caution and wait for the module to become ready or going.
+ *
+ * In the worst case, for built-in, we have to check on the module list for
+ * as many aliases possible the kernel gives the module, if that is n, that
+ * n traversals on the module list.
+ */
+static int finished_kmod_load(char *name)
+{
+ int ret = 0;
+ bool is_fs = (strlen(name) > 3) && (strncmp(name, "fs-", 3) == 0);
+
+ if (kmod_exists(name)) {
+ ret = wait_for_kmod(name);
+ } else {
+ if (is_fs && kmod_exists(name + 3))
+ ret = wait_for_kmod(name + 3);
+ }
+
+ return ret;
+}
+
/**
* __request_module - try to load a kernel module
* @wait: wait (or not) for the operation to complete
@@ -211,6 +282,8 @@ int __request_module(bool wait, const char *fmt, ...)
trace_module_request(module_name, wait, _RET_IP_);
ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
+ if (!ret)
+ ret = finished_kmod_load(module_name);
kmod_umh_threads_put();
return ret;
diff --git a/kernel/module.c b/kernel/module.c
index 07b735eed5b9c9..5c9f6d3d31ea34 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -587,8 +587,8 @@ EXPORT_SYMBOL_GPL(find_symbol);
* Search for module by name: must hold module_mutex (or preempt disabled
* for read-only access).
*/
-static struct module *find_module_all(const char *name, size_t len,
- bool even_unformed)
+struct module *find_module_all(const char *name, size_t len,
+ bool even_unformed)
{
struct module *mod;
@@ -3313,7 +3313,7 @@ static int post_relocation(struct module *mod, const struct load_info *info)
}
/* Is this module of this name done loading? No locks held. */
-static bool finished_loading(const char *name)
+bool finished_loading(const char *name)
{
struct module *mod;
bool ret;
@@ -3474,6 +3474,11 @@ static int may_init_module(void)
return 0;
}
+int module_wait_until_finished(const char *name)
+{
+ return wait_event_interruptible(module_wq, finished_loading(name));
+}
+
/*
* We try to place it in the list now to make sure it's unique before
* we dedicate too many resources. In particular, temporary percpu