* [PATCH v3] module: Fix use-after-free in module_put() tracepoint
@ 2026-11-03 16:31 Aaron Tomlin
0 siblings, 0 replies; only message in thread
From: Aaron Tomlin @ 2026-11-03 16:31 UTC (permalink / raw)
To: mcgrof, petr.pavlu, da.gomez, samitolvanen
Cc: atomlin, da.anzani, linux-modules, linux-kernel
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: stable@vger.kernel.org
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
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-03 16:31 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 16:31 [PATCH v3] module: Fix use-after-free in module_put() tracepoint Aaron Tomlin
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®