A Aaron Tomlin #
Message details and related content
A use-after-free condition can arise in module_put() should a thread be preempted immediately after decrementing the module's reference count while the module is concurrently being unloaded. In module_put(), the module's reference count is decremented via atomic_dec_if_positive() before the module pointer is passed to trace_module_put(). Consider a scenario where CPU 0 decrements the reference count without reaching the baseline MODULE_REF_BASE level and is immediately preempted: it relinquishes its reference while still retaining a local pointer to the module. If a concurrent CPU 1 subsequently drops the final active reference, the module's reference count reaches MODULE_REF_BASE. This permits the module unload sequence (via delete_module() or the initialisation-failure tear-down path) to proceed and invoke free_module(). Function free_module() invokes synchronize_rcu() and subsequently releases the memory backing struct module via free_mod_mem(). Because CPU 0 was preempted outside an RCU read-side critical section, synchronize_rcu() does not await CPU 0. When CPU 0 eventually resumes execution, it passes the dangling module pointer to trace_module_put(), which dereferences mod->name and mod->refcnt, culminating in a use-after-free. This sequence of events is illustrated below: CPU 0 CPU 1 ----- ----- module_put(module) atomic_dec_if_positive() [preempted] module_put(module) atomic_dec_if_positive() // drops final reference free_module(module) synchronize_rcu() free_mod_mem(module) trace_module_put(module, ...) mod->name Resolve this by keeping trace_module_put() after the decrement and enclosing both operations in an RCU read-side critical section via guard(rcu)(). Because free_module() invokes synchronize_rcu() before freeing the module memory, holding an RCU read lock guarantees the module structure remains valid until trace_module_put() completes. Unconditionally entering an RCU read-side critical section in module_put() would incur unnecessary overhead on the fast path when the tracepoint is inactive. Gate the RCU critical section behind trace_module_put_enabled(), which queries the tracepoint's static key via static_branch_unlikely(&__tracepoint_module_put.key) (or evaluates to constant false when CONFIG_TRACEPOINTS is disabled). When the tracepoint is not enabled, jump-label patching turns this check into a no-op instruction, bypassing guard(rcu)() and falling directly through to __module_put(). Factor the atomic decrement and warning check into a static __always_inline helper, __module_put(), to avoid duplicating logic across the traced and untraced paths. Fixes: cb0b50b813f6 ("module: Remove preempt_disable() from module reference counting.") Cc: <redacted> Signed-off-by: Aaron Tomlin <atomlin@atomlin.com> --- Changes since v2: - Retain trace_module_put() after the reference decrement so the trace event reflects the updated post-decrement reference count and only fires on successful puts - Enclose the reference decrement and trace_module_put() within an RCU read-side critical section (guard(rcu)()) to prevent the use-after-free race against synchronize_rcu() in free_module() - Gate the RCU critical section behind trace_module_put_enabled() and document in the commit message why this check is a no-op via jump-label patching when the tracepoint is disabled - Factor the atomic decrement and WARN_ON() check into a static __always_inline helper, __module_put(), eliminating code duplication between the traced and untraced paths - Link to v2: https://lore.kernel.org/lkml/20261022184517.66438-1-atomlin@atomlin.com/ Changes since v1: - Drop guard(rcu)() and relocate trace_module_put() before the atomic decrement instead, avoiding RCU overhead on every module_put() while safely relying on the caller's held reference (Petr Pavlu) - Link to v1: https://lore.kernel.org/all/20261019034857.654569-1-atomlin@atomlin.com/ --- kernel/module/main.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 34e0204ea37c..14bad822e18f 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c@@ -960,15 +960,27 @@ bool try_module_get(struct module *module) } EXPORT_SYMBOL(try_module_get); -void module_put(struct module *module) +static __always_inline void __module_put(struct module *module) { int ret; - if (module) { - ret = atomic_dec_if_positive(&module->refcnt); - WARN_ON(ret < 0); /* Failed to put refcount */ + ret = atomic_dec_if_positive(&module->refcnt); + WARN_ON(ret < 0); /* Failed to put refcount */ +} + +void module_put(struct module *module) +{ + if (!module) + return; + + if (trace_module_put_enabled()) { + guard(rcu)(); + __module_put(module); trace_module_put(module, _RET_IP_); + return; } + + __module_put(module); } EXPORT_SYMBOL(module_put);
--
2.55.0