From: Wen Yang <redacted>
We occasionally hit a lockdep "Invalid wait context" warning when
a reactor callback is preempted by a timer interrupt.
On interrupt exit the scheduler takes rq->__lock (LD_WAIT_SPIN) while
rv_react() still holds its wait-type-override map, which declared
LD_WAIT_FREE. On any kernel where the task context has preemption
enabled (not just CONFIG_PREEMPT_RT) this triggers a spurious lockdep
report:
[ BUG: Invalid wait context ]
1 lock held by kunit_try_catch/209:
#0: (rv_react_map-wait-type-override){+.+.}-{1:1}
kunit_try_catch/209 is trying to lock:
ffff8a743ed3e8a0 (&rq->__lock){-...}-{2:2}
This series fixes the wait type (patch 1), makes reactor registration
failures propagate (patch 2), adds support for module-based
reactors (patch 3), and adds KUnit/kselftest coverage (patch 4-5).
Changes in v5:
- Patch 1: Fix typo. Functional code unchanged.
- Patch 2: Unchanged.
- Patch 3: Only exported the registration helpers as v4.
- Patch 4: Fix typo. Functional code unchanged.
v5: https://lore.kernel.org/lkml/cover.1788705281.git.wen.yang@linux.dev/
v4: https://lore.kernel.org/lkml/cover.1787854397.git.wen.yang@linux.dev/
v3: https://lore.kernel.org/lkml/cover.1786294920.git.wen.yang@linux.dev/
v2: https://lore.kernel.org/lkml/cover.1785695669.git.wen.yang@linux.dev/
v1: https://lore.kernel.org/lkml/cover.1781541556.git.wen.yang@linux.dev/
Wen Yang (4):
rv/reactors: use LD_WAIT_SPIN as the reactor lockdep wait type
rv/reactors: propagate rv_register_reactor() error from reactor init
rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
rv/reactors: add KUnit tests for reactor registration and dispatch
kernel/trace/rv/Kconfig | 12 +++
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/reactor_panic.c | 3 +-
kernel/trace/rv/reactor_printk.c | 3 +-
kernel/trace/rv/rv_reactors.c | 12 ++-
kernel/trace/rv/rv_reactors_kunit.c | 111 ++++++++++++++++++++++++++++
6 files changed, 137 insertions(+), 5 deletions(-)
create mode 100644 kernel/trace/rv/rv_reactors_kunit.c
--
2.25.1
From: Wen Yang <redacted>
rv_react() overrides the lockdep wait type to LD_WAIT_FREE to enforce
that reactor callbacks take no locks. But callbacks run in the context
of the triggering tracepoint, which can be preemptible task context on
any kernel. A timer interrupt firing during the callback makes the
interrupt-exit path schedule and take rq->__lock (LD_WAIT_SPIN) while
the LD_WAIT_FREE override is still held, producing a spurious
"Invalid wait context" warning:
[ BUG: Invalid wait context ]
context-{5:5}
1 lock held by kunit_try_catch/209:
#0: (rv_react_map-wait-type-override){+.+.}-{1:1}
kunit_try_catch/209 is trying to lock:
ffff8a743ed3e8a0 (&rq->__lock){-...}-{2:2}
Use LD_WAIT_SPIN instead of LD_WAIT_FREE, which causes false-positive
warnings in preemptible contexts due to scheduler preemption taking
rq->__lock.
Fixes: 69d8895cb9a9 ("rv: Add explicit lockdep context for reactors")
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <redacted>
Cc: Thomas Weißschuh <redacted>
---
kernel/trace/rv/rv_reactors.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
From: Wen Yang <redacted>
Both register_react_printk() and register_react_panic() ignore the
return value of rv_register_reactor() and always return 0. If the
registration fails (e.g. a duplicate reactor name), the init functions
silently report success even though the reactor was not registered.
Propagate the error from rv_register_reactor() so a failed registration
is reported instead of being silently ignored.
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Reviewed-by: Nam Cao <redacted>
Signed-off-by: Wen Yang <redacted>
---
kernel/trace/rv/reactor_panic.c | 3 +--
kernel/trace/rv/reactor_printk.c | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
From: Wen Yang <redacted>
rv_react() is exported to modules, but the reactor registration helpers
are not. Export them with EXPORT_SYMBOL_GPL() so reactor modules and
the tristate KUnit test module can register and unregister reactors
without hitting undefined symbol errors at link time(modpost).
Commit 3d3800b4f7f4 ("rv: Remove reactor's reference counter") noted
that module-based reactors, if supported, should be pinned with
try_module_get()/module_put() to prevent unloading under an active
monitor. This patch does not add that support. The only in-tree module
consumer, the tristate KUnit reactor test module, registers and
unregisters its reactor within a controlled test lifecycle.
The actual pinning is left to a follow-up series that adds support for
loadable-module reactors.
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <redacted>
---
kernel/trace/rv/rv_reactors.c | 2 ++
1 file changed, 2 insertions(+)
From: Wen Yang <redacted>
Add KUnit tests covering the reactor register/unregister lifecycle
(including duplicate and name-length rejection) and rv_react() dispatch
(a no-op without a callback, exactly one invocation with one). The
mdelay() callback keeps the CPU busy so a timer interrupt lands inside
rv_react()'s lockdep context, exercising the LD_WAIT_SPIN wait type
from the previous patch; a spurious lockdep splat there would show up
in the test output.
The dispatch tests rely on reacting_on being enabled, since rv_react()
returns early when it is off.
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <redacted>
---
kernel/trace/rv/Kconfig | 12 +++
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/rv_reactors_kunit.c | 111 ++++++++++++++++++++++++++++
3 files changed, 124 insertions(+)
create mode 100644 kernel/trace/rv/rv_reactors_kunit.c
@@ -0,0 +1,111 @@+// SPDX-License-Identifier: GPL-2.0+/*+*KUnittestsforRVreactorregistrationanddispatch.+*+*Thedispatchtestsrelyonreacting_onbeingenabled,sincerv_react()+*returnsearlywhenitisoff.Itisonbydefaultwhenthesuitesrun+*built-in;asamodule,re-enableitifdisabledvia+*/sys/kernel/tracing/rv/reacting_on.+*/++#include<kunit/test.h>+#include<linux/build_bug.h>+#include<linux/rv.h>+#include<linux/delay.h>+#include"rv.h"++staticstructrv_reactortest_reactor={+.name="kunit_test_reactor",+.description="KUnit test reactor",+};++staticvoidreactor_teardown(void*arg)+{+rv_unregister_reactor(&test_reactor);+}++staticvoidregister_test_reactor(structkunit*test)+{+KUNIT_ASSERT_EQ(test,rv_register_reactor(&test_reactor),0);+KUNIT_ASSERT_EQ(test,+kunit_add_action_or_reset(test,reactor_teardown,NULL),0);+}++staticvoidtest_double_register(structkunit*test)+{+register_test_reactor(test);+KUNIT_EXPECT_EQ(test,rv_register_reactor(&test_reactor),-EINVAL);+}++staticconstcharlong_reactor_name[]="kunit_reactor_name_too_long_xxx_";+static_assert(sizeof(long_reactor_name)-1>=MAX_RV_REACTOR_NAME_SIZE,+"long_reactor_name must be at least MAX_RV_REACTOR_NAME_SIZE chars");++staticvoidtest_name_too_long(structkunit*test)+{+staticstructrv_reactorlong_reactor={+.name=long_reactor_name,+};++KUNIT_EXPECT_EQ(test,rv_register_reactor(&long_reactor),-EINVAL);+}++staticstructkunit_caserv_reactor_registration_cases[]={+KUNIT_CASE(test_double_register),+KUNIT_CASE(test_name_too_long),+{}+};++staticstructkunit_suiterv_reactor_registration_suite={+.name="rv_reactor_registration",+.test_cases=rv_reactor_registration_cases,+};++staticintreact_call_count;++__printf(1,0)staticvoidmock_react(constchar*msg,va_listargs)+{+react_call_count++;+/* Busy-wait so a timer interrupt fires inside rv_react(). */+mdelay(20);+}++staticvoidtest_react_no_callback(structkunit*test)+{+structrv_monitormonitor={+.name="kunit_null_react",+};++react_call_count=0;+rv_react(&monitor,"no callback");++KUNIT_EXPECT_EQ(test,react_call_count,0);+}++staticvoidtest_react_callback_invoked(structkunit*test)+{+structrv_monitormonitor={+.name="kunit_dispatch_monitor",+.react=mock_react,+};++react_call_count=0;+rv_react(&monitor,"callback invocation test");+KUNIT_EXPECT_EQ(test,react_call_count,1);+}++staticstructkunit_caserv_react_dispatch_cases[]={+KUNIT_CASE(test_react_no_callback),+KUNIT_CASE(test_react_callback_invoked),+{}+};++staticstructkunit_suiterv_react_dispatch_suite={+.name="rv_react_dispatch",+.test_cases=rv_react_dispatch_cases,+};++kunit_test_suites(&rv_reactor_registration_suite,&rv_react_dispatch_suite);++MODULE_LICENSE("GPL");+MODULE_DESCRIPTION("KUnit tests for RV reactor registration and dispatch");
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Exporting `rv_register_reactor` and `rv_unregister_reactor` allows loadable modules to register reactors, but failing to detach the reactor from active monitors creates a critical execute-after-free vulnerability when the module is unloaded.
--
commit 516262534066676c40b564e9e3018cd406dd5532
Author: Wen Yang [off-list ref]
rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
This commit exports rv_register_reactor() and rv_unregister_reactor()
with EXPORT_SYMBOL_GPL() so that reactor modules and the tristate KUnit
test module can register and unregister reactors.
@@ -314,6 +314,7 @@ int rv_register_reactor(struct rv_reactor *reactor)guard(mutex)(&rv_interface_lock);return__rv_register_reactor(reactor);}+EXPORT_SYMBOL_GPL(rv_register_reactor);
[Severity: High]
Does exporting this API without module pinning allow an execute-after-free
if a module is unloaded?
Although the commit message notes that actual pinning is left to a follow-up
series, exporting the API in this patch makes it immediately usable and
inherently unsafe for any module. This includes the in-tree KUnit module,
which can race with a sysfs write during its test execution.
quoted hunk
/**
* rv_unregister_reactor - unregister a rv reactor.
[Severity: High]
Can unregistering a reactor from a loadable module leave a dangling pointer
in an active monitor?
If a loadable module registers a reactor, and userspace assigns it to an
active monitor via /sys/kernel/tracing/rv/monitors/*/reactors, unloading
the module calls rv_unregister_reactor().
This function removes the reactor from the global list but fails to detach
it from the active monitor. When the monitor subsequently triggers, it will
dereference the freed memory in rv_react():
kernel/trace/rv/rv_reactors.c:rv_react() {
...
monitor->react(msg, args);
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789412799.git.wen.yang@linux.dev?part=3
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing module reference counting for RV reactors leads to a Use-After-Free if a reactor module is unloaded while still assigned to a monitor.
--
commit ad2a08e6decf463e8a9bfc030b93a95b3cf6f3b1
Author: Wen Yang [off-list ref]
rv/reactors: add KUnit tests for reactor registration and dispatch
Add KUnit tests covering the reactor register/unregister lifecycle
(including duplicate and name-length rejection) and rv_react() dispatch
(a no-op without a callback, exactly one invocation with one).
[Severity: High]
This is a pre-existing issue, but could rv_unregister_reactor() leave
dangling pointers if a dynamically loaded reactor module is unloaded while
still assigned to a monitor?
Looking at rv_unregister_reactor() in kernel/trace/rv/rv_reactors.c, it
removes the reactor from the global list but does not appear to iterate over
active monitors to detach it.
Furthermore, in monitor_swap_reactors_single(), the reactor is assigned to
the monitor without taking a module reference count via try_module_get():
kernel/trace/rv/rv_reactors.c:monitor_swap_reactors_single() {
...
mon->reactor = reactor;
mon->react = reactor->react;
...
}
If a user with tracefs access assigns a dynamically loaded reactor (e.g.,
reactor_printk.ko) to an RV monitor and then unloads the reactor module,
when the monitor's tracepoint fires, rv_react() would invoke the dangling
monitor->react function pointer.
Could this jump into the unmapped memory of the unloaded module and cause
a use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789412799.git.wen.yang@linux.dev?part=4
On Tue, 15 Sep 2026 03:13:33 +0800, wen.yang@linux.dev wrote:
From: Wen Yang <redacted>
We occasionally hit a lockdep "Invalid wait context" warning when
a reactor callback is preempted by a timer interrupt.
On interrupt exit the scheduler takes rq->__lock (LD_WAIT_SPIN) while
rv_react() still holds its wait-type-override map, which declared
LD_WAIT_FREE. On any kernel where the task context has preemption
enabled (not just CONFIG_PREEMPT_RT) this triggers a spurious lockdep
report:
[...]
Applied to my tree, thanks!
[1/4] rv/reactors: use LD_WAIT_SPIN as the reactor lockdep wait type
commit: 16249b9b6be1f05e7d4116459a527ee681d2ff9f
[2/4] rv/reactors: propagate rv_register_reactor() error from reactor init
commit: 7439018693dd41b6578a4a036cb463e1388b7ab0
[3/4] rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
commit: 1863477db0cdad202049ce66b1a0036a87fe16dc
[4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
commit: 8b8129a31089b2430a77debea97772bb2402779f
Best regards,
--
Gabriele Monaco [off-list ref]