[RFC PATCH] kernel/module: add a safer implementation of try_module_get()

From: Marco Pagani
Date: Tue Jan 30 2024 - 14:36:39 EST


The current implementation of try_module_get() requires the module to
exist and be live as a precondition. While this may seem intuitive at
first glance, enforcing the precondition can be tricky, considering that
modules can be unloaded at any time if not previously taken. For
instance, the caller could be preempted just before calling
try_module_get(), and while preempted, the module could be unloaded and
freed. More subtly, the module could also be unloaded at any point while
executing try_module_get() before incrementing the refount with
atomic_inc_not_zero().

Neglecting the precondition that the module must exist and be live can
cause unexpected race conditions that can lead to crashes. However,
ensuring that the precondition is met may require additional locking
that increases the complexity of the code and can make it more
error-prone.

This patch adds a slower yet safer implementation of try_module_get()
that checks if the module is valid by looking into the mod_tree before
taking the module's refcount. This new function can be safely called on
stale and invalid module pointers, relieving developers from the burden
of ensuring that the module exists and is live before attempting to take
it.

The tree lookup and refcount increment are executed after taking the
module_mutex to prevent the module from being unloaded after looking up
the tree.

Signed-off-by: Marco Pagani <marpagan@xxxxxxxxxx>
---
include/linux/module.h | 15 +++++++++++++++
kernel/module/main.c | 27 +++++++++++++++++++++++++++
2 files changed, 42 insertions(+)

diff --git a/include/linux/module.h b/include/linux/module.h
index 08364d5cbc07..86b6ea43d204 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -695,6 +695,19 @@ extern void __module_get(struct module *module);
*/
extern bool try_module_get(struct module *module);

+/**
+ * try_module_get_safe() - safely take the refcount of a module.
+ * @module: address of the module to be taken.
+ *
+ * Safer version of try_module_get(). Check first if the module exists and is alive,
+ * and then take its reference count.
+ *
+ * Return:
+ * * %true - module exists and its refcount has been incremented or module is NULL.
+ * * %false - module does not exist.
+ */
+extern bool try_module_get_safe(struct module *module);
+
/**
* module_put() - release a reference count to a module
* @module: the module we should release a reference count for
@@ -815,6 +828,8 @@ static inline bool try_module_get(struct module *module)
return true;
}

+#define try_module_get_safe(module) try_module_get(module)
+
static inline void module_put(struct module *module)
{
}
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 98fedfdb8db5..22376b69778c 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -842,6 +842,33 @@ bool try_module_get(struct module *module)
}
EXPORT_SYMBOL(try_module_get);

+bool try_module_get_safe(struct module *module)
+{
+ struct module *mod;
+ bool ret = true;
+
+ if (!module)
+ goto out;
+
+ mutex_lock(&module_mutex);
+
+ /*
+ * Check if the address points to a valid live module and take
+ * the refcount only if it points to the module struct.
+ */
+ mod = __module_address((unsigned long)module);
+ if (mod && mod == module && module_is_live(mod))
+ __module_get(mod);
+ else
+ ret = false;
+
+ mutex_unlock(&module_mutex);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(try_module_get_safe);
+
void module_put(struct module *module)
{
int ret;

base-commit: 4515d08a742c76612b65d2f47a87d12860519842
--
2.43.0