From: Wen Yang <wen.yang@linux.dev>
To: XIAO WU <xiaowu.417@qq.com>, Gabriele Monaco <gmonaco@redhat.com>
Cc: Nam Cao <namcao@linutronix.de>,
linux-trace-kernel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 3/3] rv/reactors: add KUnit tests for reactor_panic
Date: Sun, 21 Jun 2026 11:34:37 +0800 [thread overview]
Message-ID: <2e637e73-1756-4923-b81d-2378c114e5a5@linux.dev> (raw)
In-Reply-To: <tencent_913194D0B2365EB0E404E464443D38FDC607@qq.com>
On 6/21/26 07:30, XIAO WU wrote:
> Hi Wen,
>
> I came across a Sashiko AI code review [1] that flagged a potential NULL
> pointer dereference in the `test_panic_register_unregister()` test case
> added by this patch (commit 8655782285e2). The review's analysis seemed
> plausible, so I spun up a QEMU environment to see whether it could be
> reproduced in practice.
>
> The short version: yes, it triggers a real kernel BUG + Oops. See below
> for the crash log and the reproduction approach.
>
> On Tue, 16 Jun 2026 at 00:44, Wen Yang wrote:
> > Add KUnit tests for the panic reactor covering:
> > - Reactor registration and unregistration lifecycle
> > - Panic notifier chain reachability
> ...
> > +static void test_panic_register_unregister(struct kunit *test)
> > +{
> > + int ret;
> > +
> > + ret = rv_register_reactor(&mock_panic_reactor);
> > + KUNIT_EXPECT_EQ(test, ret, 0);
> > + KUNIT_EXPECT_STREQ(test, mock_panic_reactor.name, "test_panic");
> > +
> > + rv_unregister_reactor(&mock_panic_reactor);
>
> This is the function the review highlighted. The issue is:
>
> - `KUNIT_EXPECT_EQ()` does *not* abort the test on failure.
> - If `rv_register_reactor()` fails (e.g. because another reactor
> named "test_panic" was already registered), the .list node of the
> statically-allocated `mock_panic_reactor` is never added to any
> list — it remains zero-initialized (prev = NULL, next = NULL).
> - `rv_unregister_reactor()` then unconditionally calls `list_del()`
> on this uninitialized list_head, which hits the NULL pointers.
>
> I was able to reproduce this reliably. The trigger condition is
> surprisingly simple: if any code path registers a reactor named
> "test_panic" before the KUnit suite runs, the test crashes the kernel.
>
> [Reproduction approach]
>
> I rebuilt the kernel with a small late_initcall in rv_reactors.c that
> pre-registers "test_panic" (simulating what would happen if, say, a
> kernel module or another subsystem registered a reactor with the same
> name before the KUnit tests execute):
>
> static int __init prereg_test_panic(void)
> {
> static struct rv_reactor prereg = {
> .name = "test_panic",
> .description = "pre-registered to simulate name collision",
> };
> return rv_register_reactor(&prereg);
> }
> late_initcall(prereg_test_panic);
>
> The KUnit tests then auto-run at boot (kunit_run_all_tests). The
> test_panic_register_unregister case fails registration with -EINVAL due
> to the duplicate name, the KUNIT_EXPECT_EQ does not abort, and
> rv_unregister_reactor() crashes on the uninitialized list.
>
> [Crash log — kernel 7.1.0-next-20260615, CONFIG_DEBUG_LIST=y]
>
> Reactor test_panic is already registered
> # test_panic_register_unregister: EXPECTATION FAILED at
> kernel/trace/rv/reactor_panic_kunit.c:68
> Expected ret == 0, but
> ret == -22 (0xffffffffffffffea)
> list_del corruption, ffffffff8ecce2f8->next is NULL
> ------------[ cut here ]------------
> kernel BUG at lib/list_debug.c:52!
> Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
> CPU: 1 UID: 0 PID: 5028 Comm: kunit_try_catch Tainted: G N
> RIP: 0010:__list_del_entry_valid_or_report+0xf2/0x200
> Call Trace:
> <TASK>
> rv_unregister_reactor+0x37/0x190
> test_panic_register_unregister+0x1de/0x2e0
> kunit_try_run_case+0x1d2/0x520
> kunit_generic_run_threadfn_adapter+0x89/0x100
> kthread+0x387/0x4a0
> ret_from_fork+0xb2c/0xdd0
> </TASK>
> Kernel panic - not syncing: Fatal exception
>
> The crash is in `rv_unregister_reactor()`, called from
> `test_panic_register_unregister()`. The `list_del()` in
> `rv_unregister_reactor()` has no guard against a list node that was
> never added to any list. With CONFIG_DEBUG_LIST=y the corruption is
> caught explicitly; without it this would be a silent NULL dereference.
>
> [Suggested fix]
>
> The most straightforward fix is to use `KUNIT_ASSERT_EQ()` instead of
> `KUNIT_EXPECT_EQ()` for the registration result, so the test aborts
> before reaching `rv_unregister_reactor()` on a failed registration:
>
> static void test_panic_register_unregister(struct kunit *test)
> {
> int ret;
>
> ret = rv_register_reactor(&mock_panic_reactor);
> - KUNIT_EXPECT_EQ(test, ret, 0);
> + KUNIT_ASSERT_EQ(test, ret, 0);
> KUNIT_EXPECT_STREQ(test, mock_panic_reactor.name, "test_panic");
>
> rv_unregister_reactor(&mock_panic_reactor);
> }
>
> An alternative (or complementary) approach would be to add a guard in
> `rv_unregister_reactor()` itself — e.g. checking whether the reactor is
> actually on the list before calling `list_del()`. That would make the
> API more robust against future callers making the same mistake.
>
> The same pattern likely applies to the printk reactor tests in patch
> 2/3, though I haven't tested those.
>
Okay, thank you.
We've noted this is related to a Kunit test. We'll incorporate the
improvement in the v2.
Thanks again.
--
Wen
> Full PoC code follows.
>
> [PoC part 1 — Kernel-space: late_initcall to create the name collision]
>
> This is what was added to kernel/trace/rv/rv_reactors.c (or could be
> built as a standalone kernel module — see preregister.c below). It
> pre-registers "test_panic" before KUnit auto-runs, so the test's own
> rv_register_reactor() fails with -EINVAL:
>
> static int __init prereg_test_panic(void)
> {
> static struct rv_reactor prereg = {
> .name = "test_panic",
> .description = "pre-registered to simulate name collision",
> };
> return rv_register_reactor(&prereg);
> }
> late_initcall(prereg_test_panic);
>
> [PoC part 2 — Userspace: trigger the KUnit test via debugfs]
>
> poc.c:
> ---8<----------------------------------------------------------------
> /*
> * POC: NULL pointer dereference in rv_unregister_reactor()
> *
> * Bug location: kernel/trace/rv/reactor_panic_kunit.c
> * test_panic_register_unregister()
> *
> * Bug: When rv_register_reactor() fails (because "test_panic" is already
> * registered), the test calls rv_unregister_reactor() unconditionally.
> * This performs list_del() on a zero-initialized list_head (never added
> * to any list), causing a NULL pointer dereference crash.
> *
> * Trigger: With a kernel that has pre-registered "test_panic" reactor,
> * simply trigger the KUnit test via debugfs "run" file. The test's
> * rv_register_reactor() fails with -EINVAL (duplicate), and the
> subsequent
> * rv_unregister_reactor() crashes on the uninitialized list.
> */
>
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> #include <fcntl.h>
> #include <errno.h>
>
> #define KUNIT_RUN_PATH "/sys/kernel/debug/kunit/rv_reactor_panic/run"
>
> int main(int argc, char **argv)
> {
> int fd, ret;
>
> setbuf(stdout, NULL);
>
> printf("[+] POC: Triggering NULL deref in rv_unregister_reactor\n");
> printf("[+] Target: %s\n\n", KUNIT_RUN_PATH);
>
> /* Mount debugfs if needed */
> if (access("/sys/kernel/debug", F_OK) != 0) {
> printf("[*] Mounting debugfs...\n");
> ret = system("mount -t debugfs none /sys/kernel/debug/
> 2>/dev/null");
> (void)ret;
> }
>
> /* Verify KUnit path exists */
> if (access(KUNIT_RUN_PATH, W_OK) != 0) {
> printf("[-] Cannot access %s: %m\n", KUNIT_RUN_PATH);
> printf("[*] Available KUnit suites:\n");
> fflush(stdout);
> system("ls -la /sys/kernel/debug/kunit/ 2>&1");
> return 1;
> }
>
> printf("[*] Test_panic reactor should be pre-registered at boot\n");
> printf("[*] Triggering KUnit test suite...\n\n");
>
> /*
> * Write to the KUnit run file. This executes
> * __kunit_test_suites_init() -> kunit_run_tests() which
> * runs the reactor_panic_kunit test cases including
> * test_panic_register_unregister.
> *
> * With "test_panic" pre-registered:
> * 1. rv_register_reactor() returns -EINVAL (duplicate)
> * 2. KUNIT_EXPECT_EQ doesn't abort
> * 3. rv_unregister_reactor() calls list_del() on NULL list
> * 4. BOOM: list corruption / NULL deref / kernel crash
> */
> fd = open(KUNIT_RUN_PATH, O_WRONLY);
> if (fd < 0) {
> printf("[-] open failed: %m\n");
> return 1;
> }
>
> printf("[!] Writing to %s - triggering the crash now...\n",
> KUNIT_RUN_PATH);
> fflush(stdout);
>
> ret = write(fd, "1", 1);
> if (ret < 0) {
> printf("[-] write failed: %m\n");
> } else {
> printf("[+] Write succeeded (ret=%d)\n", ret);
> }
>
> close(fd);
>
> /*
> * If we reach here without crashing, let the user know
> */
> printf("\n[*] If the system is still alive, check dmesg:\n");
> printf(" dmesg | grep -i -E
> 'list_del|list_add|list_corrupt|NULL|BUG|oops\n");
> printf("\n[*] dmesg output:\n");
> fflush(stdout);
> system("dmesg | tail -60");
>
> printf("\n[+] POC completed.\n");
>
> return 0;
> }
> ---8<----------------------------------------------------------------
>
> Compile with:
> gcc -o poc poc.c -static
>
> [PoC part 3 — Kernel module alternative (standalone)]
>
> If you prefer not to modify rv_reactors.c directly, the same name
> collision can be created by loading this module before running the
> KUnit test. Note: this requires rv_register_reactor() to be exported
> (or resolved via kallsyms), which it may not be in the current tree.
> In that case the late_initcall approach above is the way to go.
>
> preregister.c:
> ---8<----------------------------------------------------------------
> #include <linux/module.h>
> #include <linux/kernel.h>
> #include <linux/rv.h>
>
> static struct rv_reactor prereg_reactor = {
> .name = "test_panic",
> .description = "pre-registered to trigger KUnit bug",
> };
>
> static int __init prereg_init(void)
> {
> int ret;
> ret = rv_register_reactor(&prereg_reactor);
> if (ret < 0) {
> pr_err("preregister: rv_register_reactor failed: %d\n", ret);
> return ret;
> }
> pr_info("preregister: registered 'test_panic' reactor\n");
> return 0;
> }
>
> static void __exit prereg_exit(void)
> {
> rv_unregister_reactor(&prereg_reactor);
> pr_info("preregister: unregistered 'test_panic' reactor\n");
> }
>
> module_init(prereg_init);
> module_exit(prereg_exit);
> MODULE_LICENSE("GPL");
> ---8<----------------------------------------------------------------
>
>
> [1]
> https://sashiko.dev/#/patchset/cover.1781541556.git.wen.yang%40linux.dev
> (Sashiko AI code review — "Null Pointer Dereference", Severity: High)
>
> Thanks,
> XIAO
>
next prev parent reply other threads:[~2026-06-21 3:34 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-15 16:44 [PATCH 0/3] rv/reactors: fix lockdep warning and add KUnit tests wen.yang
2026-06-15 16:44 ` [PATCH 1/3] rv/reactors: fix lockdep "Invalid wait context" in rv_react() wen.yang
2026-06-17 11:12 ` Nam Cao
2026-06-17 15:58 ` Nam Cao
2026-06-23 9:38 ` Thomas Weißschuh
2026-07-06 14:02 ` Gabriele Monaco
2026-07-08 15:47 ` Thomas Weißschuh
2026-07-09 16:37 ` Wen Yang
2026-07-09 18:07 ` Nam Cao
2026-07-14 17:39 ` Wen Yang
2026-06-15 16:44 ` [PATCH 2/3] rv/reactors: add KUnit tests for reactor_printk wen.yang
2026-06-23 9:54 ` Thomas Weißschuh
2026-07-06 14:41 ` Gabriele Monaco
2026-07-09 16:53 ` Wen Yang
2026-06-15 16:44 ` [PATCH 3/3] rv/reactors: add KUnit tests for reactor_panic wen.yang
2026-06-20 23:30 ` XIAO WU
2026-06-21 3:34 ` Wen Yang [this message]
2026-07-06 14:48 ` Gabriele Monaco
2026-07-06 15:00 ` Gabriele Monaco
2026-07-09 16:46 ` Wen Yang
2026-07-10 8:27 ` Gabriele Monaco
2026-06-17 15:41 ` [PATCH 0/3] rv/reactors: fix lockdep warning and add KUnit tests Gabriele Monaco
2026-06-17 15:52 ` Nam Cao
2026-06-17 16:14 ` Gabriele Monaco
2026-06-17 17:11 ` Wen Yang
2026-06-18 15:35 ` Gabriele Monaco
2026-06-20 9:13 ` Wen Yang
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=2e637e73-1756-4923-b81d-2378c114e5a5@linux.dev \
--to=wen.yang@linux.dev \
--cc=gmonaco@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=namcao@linutronix.de \
--cc=xiaowu.417@qq.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 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.