From: Aaron Tomlin <atomlin@atomlin.com>
To: mcgrof@kernel.org, petr.pavlu@suse.com, da.gomez@kernel.org,
samitolvanen@google.com
Cc: atomlin@atomlin.com, da.anzani@gmail.com,
linux-modules@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v3] module: Fix use-after-free in module_put() tracepoint
Date: Tue, 3 Nov 2026 11:31:03 -0500 [thread overview]
Message-ID: <20261103163103.13195-1-atomlin@atomlin.com> (raw)
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
reply other threads:[~2026-11-03 16:31 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261103163103.13195-1-atomlin@atomlin.com \
--to=atomlin@atomlin.com \
--cc=da.anzani@gmail.com \
--cc=da.gomez@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-modules@vger.kernel.org \
--cc=mcgrof@kernel.org \
--cc=petr.pavlu@suse.com \
--cc=samitolvanen@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®