From: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>
To: Steven Rostedt <rostedt@goodmis.org>,
Masami Hiramatsu <mhiramat@kernel.org>,
Naveen N Rao <naveen@kernel.org>,
"David S . Miller" <davem@davemloft.net>
Cc: linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
Mani Tofigh <mt4022@columbia.edu>
Subject: [PATCH v3 5/5] kprobes: Add KUnit test cases for kprobes regressions
Date: Wed, 11 Nov 2026 23:14:08 +0900 [thread overview]
Message-ID: <179440644868.328088.1235522965655007009.stgit@devnote2> (raw)
In-Reply-To: <179440640176.328088.17106877243326801914.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add KUnit test cases to lib/tests/test_kprobes.c to verify that the
recent regressions in kprobes registration, ftrace ops switching, and
optimization state transitions are resolved:
1. test_kretprobe_unregistered_cleanup:
Verifies that unregistering an uninitialized or already-unregistered
kretprobe does not cause a NULL pointer dereference or double free.
2. test_kretprobe_rereg_by_symbol:
Verifies that attempting to re-register an active kretprobe by symbol
name fails with -EINVAL and does not destroy the active probe's
underlying rethook state.
3. test_kprobe_ftrace_post_handler_switch:
Verifies that adding a second kprobe with a post_handler to a probe
point armed with kprobe_ftrace_ops correctly switches to
kprobe_ipmodify_ops and both probes cleanly unregister without
disarming errors (-ENOENT).
4. test_optprobe_rearm_unoptimizing:
Verifies that disabling an optimized kprobe on a notrace target and
immediately re-enabling it before the optimizer thread runs works
cleanly without overwriting the jump instruction or crashing.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Check that the jump opcode at *opt_kp.addr is preserved across
disable_kprobe() and enable_kprobe() in
test_optprobe_rearm_unoptimizing().
Changes in v2:
- Probe a notrace target function (kprobe_opt_target) in
test_optprobe_rearm_unoptimizing() so that jump optimization is tested
even when CONFIG_KPROBES_ON_FTRACE=y.
- Guard test_optprobe_rearm_unoptimizing() with
!IS_MODULE(CONFIG_KPROBES_SANITY_TEST) since
wait_for_kprobe_optimizer() is not exported to modules.
- Replace KUNIT_ASSERT_EQ() with KUNIT_EXPECT_EQ() while stack-allocated
probes are registered to prevent stack use-after-free on test failure.
---
lib/tests/test_kprobes.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/lib/tests/test_kprobes.c b/lib/tests/test_kprobes.c
index 06e729e4de05..df3f080b1852 100644
--- a/lib/tests/test_kprobes.c
+++ b/lib/tests/test_kprobes.c
@@ -358,8 +358,136 @@ static void test_stacktrace_on_nested_kretprobe(struct kunit *test)
}
#endif /* CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE */
+/* Test for: kprobes: Fix double free and NULL deref in unregister_kretprobes() */
+static void test_kretprobe_unregistered_cleanup(struct kunit *test)
+{
+ struct kretprobe unreg_rp = {
+ .kp.symbol_name = "kprobe_target",
+ };
+
+ current_test = test;
+
+ /* Unregistering a probe that was never registered must return cleanly */
+ unregister_kretprobe(&unreg_rp);
+
+ /* Double unregister of a valid registered probe */
+ KUNIT_ASSERT_EQ(test, 0, register_kretprobe(&unreg_rp));
+ unregister_kretprobe(&unreg_rp);
+ /* Second unregister must not cause double-free or NULL pointer dereference */
+ unregister_kretprobe(&unreg_rp);
+}
+
+/* Test for: kprobes: Validate re-registration earlier in register_kretprobe() */
+static void test_kretprobe_rereg_by_symbol(struct kunit *test)
+{
+ struct kretprobe test_rp = {
+ .kp.symbol_name = "kprobe_target",
+ .handler = return_handler,
+ .entry_handler = entry_handler,
+ };
+
+ current_test = test;
+ KUNIT_ASSERT_EQ(test, 0, register_kretprobe(&test_rp));
+
+ /* Verify the probe is live and functional */
+ krph_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_EQ(test, krph_val, rand1);
+
+ /*
+ * Attempt re-registration with symbol_name (with kp.addr cleared).
+ * Must fail with -EINVAL and NOT destroy the live probe's rethook.
+ */
+ test_rp.kp.addr = NULL;
+ KUNIT_EXPECT_EQ(test, -EINVAL, register_kretprobe(&test_rp));
+
+ /* The original kretprobe must still be functional */
+ krph_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_EQ(test, krph_val, rand1);
+
+ unregister_kretprobe(&test_rp);
+}
+
#endif /* CONFIG_KRETPROBES */
+#if defined(CONFIG_KPROBES_ON_FTRACE)
+/* Test for: kprobes: Switch ftrace ops when adding probe with post_handler */
+static void test_kprobe_ftrace_post_handler_switch(struct kunit *test)
+{
+ struct kprobe kp_entry = {
+ .symbol_name = "kprobe_target",
+ .pre_handler = kp_pre_handler,
+ };
+ struct kprobe kp_post = {
+ .symbol_name = "kprobe_target",
+ .post_handler = kp_post_handler,
+ };
+ int ret;
+
+ current_test = test;
+
+ /* 1. Register first probe without post_handler (armed with kprobe_ftrace_ops) */
+ KUNIT_ASSERT_EQ(test, 0, register_kprobe(&kp_entry));
+
+ /* 2. Register second probe with post_handler at same IP (must switch ops) */
+ ret = register_kprobe(&kp_post);
+ KUNIT_EXPECT_EQ(test, 0, ret);
+ if (!ret) {
+ /* 3. Execute target function to trigger both */
+ preh_val = 0;
+ posth_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_NE(test, 0, preh_val);
+ KUNIT_EXPECT_NE(test, 0, posth_val);
+
+ /* 4. Unregistering must successfully disarm without -ENOENT */
+ unregister_kprobe(&kp_post);
+ }
+ unregister_kprobe(&kp_entry);
+}
+#endif
+
+#if defined(CONFIG_OPTPROBES) && !IS_MODULE(CONFIG_KPROBES_SANITY_TEST)
+static u32 (*opt_target)(u32 value);
+
+static noinline notrace u32 kprobe_opt_target(u32 value)
+{
+ return (value / div_factor) + 2;
+}
+
+/* Test for: kprobes: Do not overwrite jump instruction when re-arming unoptimizing probe */
+static void test_optprobe_rearm_unoptimizing(struct kunit *test)
+{
+ struct kprobe opt_kp = {
+ .symbol_name = "kprobe_opt_target",
+ .pre_handler = kp_pre_handler,
+ };
+ kprobe_opcode_t jmp_op;
+
+ current_test = test;
+ KUNIT_ASSERT_EQ(test, 0, register_kprobe(&opt_kp));
+
+ /* Allow background optimizer thread to jump-optimize the probe */
+ wait_for_kprobe_optimizer();
+ jmp_op = READ_ONCE(*opt_kp.addr);
+
+ /* Temporarily disable probe (queues on unoptimizing_list, leaves jump) */
+ KUNIT_EXPECT_EQ(test, 0, disable_kprobe(&opt_kp));
+
+ /* Immediately re-enable (re-arm) before optimizer thread unoptimizes */
+ KUNIT_EXPECT_EQ(test, 0, enable_kprobe(&opt_kp));
+
+ /* Verify jump opcode was not overwritten by a breakpoint on re-arm */
+ KUNIT_EXPECT_EQ(test, jmp_op, READ_ONCE(*opt_kp.addr));
+ preh_val = 0;
+ opt_target(rand1);
+ KUNIT_EXPECT_NE(test, 0, preh_val);
+
+ unregister_kprobe(&opt_kp);
+}
+#endif
+
static int kprobes_test_init(struct kunit *test)
{
KP_CLEAR(kp);
@@ -380,6 +508,9 @@ static int kprobes_test_init(struct kunit *test)
stacktrace_target = kprobe_stacktrace_target;
internal_target = kprobe_stacktrace_internal_target;
stacktrace_driver = kprobe_stacktrace_driver;
+#if defined(CONFIG_OPTPROBES) && !IS_MODULE(CONFIG_KPROBES_SANITY_TEST)
+ opt_target = kprobe_opt_target;
+#endif
rand1 = get_random_u32_above(div_factor);
return 0;
}
@@ -391,10 +522,18 @@ static struct kunit_case kprobes_testcases[] = {
#ifdef CONFIG_KRETPROBES
KUNIT_CASE(test_kretprobe),
KUNIT_CASE(test_kretprobes),
+ KUNIT_CASE(test_kretprobe_unregistered_cleanup),
+ KUNIT_CASE(test_kretprobe_rereg_by_symbol),
#ifdef CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE
KUNIT_CASE(test_stacktrace_on_kretprobe),
KUNIT_CASE(test_stacktrace_on_nested_kretprobe),
#endif
+#endif
+#if defined(CONFIG_KPROBES_ON_FTRACE)
+ KUNIT_CASE(test_kprobe_ftrace_post_handler_switch),
+#endif
+#if defined(CONFIG_OPTPROBES) && !IS_MODULE(CONFIG_KPROBES_SANITY_TEST)
+ KUNIT_CASE(test_optprobe_rearm_unoptimizing),
#endif
{}
};
prev parent reply other threads:[~2026-11-11 14:14 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 14:13 [PATCH v3 0/5] kprobes: Fixes for ftrace ops and optprobe state machine Masami Hiramatsu (Google)
2026-11-11 14:13 ` [PATCH v3 1/5] kprobes: Switch ftrace ops when adding probe with post_handler Masami Hiramatsu (Google)
2026-11-11 14:13 ` [PATCH v3 2/5] kprobes: Fix UAF and queue cooling probes on forced unoptimization Masami Hiramatsu (Google)
2026-11-11 14:13 ` [PATCH v3 3/5] kprobes: Do not overwrite jump instruction when re-arming unoptimizing probe Masami Hiramatsu (Google)
2026-11-11 14:13 ` [PATCH v3 4/5] kprobes: Re-optimize blocked sibling probe when cooling finishes Masami Hiramatsu (Google)
2026-11-11 14:14 ` Masami Hiramatsu (Google) [this message]
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=179440644868.328088.1235522965655007009.stgit@devnote2 \
--to=mhiramat@kernel.org \
--cc=davem@davemloft.net \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mt4022@columbia.edu \
--cc=naveen@kernel.org \
--cc=rostedt@goodmis.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.