public inbox for linux-trace-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Shijia Hu <hushijia1@uniontech.com>
To: mhiramat@kernel.org, naveen@kernel.org, davem@davemloft.net
Cc: ananth@in.ibm.com, akpm@linux-foundation.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	hushijia1@uniontech.com
Subject: [PATCH] kprobes: Remove dead child probes from aggrprobe list on module unload
Date: Wed, 29 Apr 2026 11:29:19 +0800	[thread overview]
Message-ID: <20260429032919.208790-1-hushijia1@uniontech.com> (raw)

When a kernel module that registered kprobes is unloaded without calling
unregister_kprobe(), kprobes_module_callback() calls kill_kprobe() to
mark the probe(s) GONE.  If the probe is an aggrprobe, kill_kprobe()
also marks all child probes GONE, but it does not remove them from
the aggrprobe's list.

The problem is that child probes whose struct kprobe resides in the
unloading module's memory are freed along with the module, yet they
remain on the aggrprobe's list.  Later, when another caller registers
a kprobe at the same address, __get_valid_kprobe() walks that list
and dereferences the freed child probe, causing a use-after-free.

Reproduction steps:

    1) Load module A which registers two kprobes on the same kernel
       function address (e.g., do_nanosleep), causing them to be
       aggregated under one aggrprobe.

    2) Unload module A without calling unregister_kprobe().
       Module A's memory is freed, but its two child probes remain
       on the aggrprobe's list as dangling pointers.

    3) Load module B and register a kprobe on the same address
       (e.g., do_nanosleep). register_kprobe() -> __get_valid_kprobe()
       traverses the aggrprobe's list and dereferences the freed child
       probe from module A, triggering a use-after-free and kernel panic.

The resulting crash looks like:
    [  464.950864] BUG: kernel NULL pointer dereference, address: 0000000000000000
    [  464.950872] #PF: supervisor read access in kernel mode
    [  464.950874] #PF: error_code(0x0000) - not-present page
    ...
    [  464.950915] Call Trace:
    [  464.950922]  <TASK>
    [  464.950923]  register_kprobe+0x65/0x2e0
    [  464.950928]  ? __pfx_stage2_init+0x10/0x10 [kprobe_leak_stage2]
    [  464.950933]  stage2_init+0x37/0xff0 [kprobe_leak_stage2]
    [  464.950938]  ? __pfx_stage2_init+0x10/0x10 [kprobe_leak_stage2]
    [  464.950942]  do_one_initcall+0x56/0x2e0
    [  464.950948]  do_init_module+0x60/0x230
    ...

  Fix this by adding selective cleanup in kprobes_module_callback():
  after calling kill_kprobe() on the aggrprobe, iterate its child list
  and remove any child probe whose struct kprobe is inside the going
  module's memory range (within_module_init / within_module_core).

  This is done in kprobes_module_callback() rather than kill_kprobe()
  because kill_kprobe()'s semantic is "the probed code is going away,
  mark probes GONE".  The lifetime of a probe is bound to the probed
  code, not to the module containing the struct kprobe.  Child probes
  owned by other still-loaded modules or by kmalloc (ftrace, perf,
  kprobe-events) must stay on the list so they can be unregistered
  later.  Only child probes whose memory is about to be freed need to
  be removed from the list to prevent dangling pointers.

Fixes: e8386a0cb22f4 ("kprobes: support probing module __exit function")
Signed-off-by: Shijia Hu <hushijia1@uniontech.com>
---
 kernel/kprobes.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index bfc89083daa9..ff277314183c 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -2664,6 +2664,7 @@ static int kprobes_module_callback(struct notifier_block *nb,
 				   unsigned long val, void *data)
 {
 	struct module *mod = data;
+	struct hlist_node *tmp;
 	struct hlist_head *head;
 	struct kprobe *p;
 	unsigned int i;
@@ -2685,7 +2686,7 @@ static int kprobes_module_callback(struct notifier_block *nb,
 	 */
 	for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
 		head = &kprobe_table[i];
-		hlist_for_each_entry(p, head, hlist)
+		hlist_for_each_entry_safe(p, tmp, head, hlist) {
 			if (within_module_init((unsigned long)p->addr, mod) ||
 			    (checkcore &&
 			     within_module_core((unsigned long)p->addr, mod))) {
@@ -2702,6 +2703,26 @@ static int kprobes_module_callback(struct notifier_block *nb,
 				 */
 				kill_kprobe(p);
 			}
+
+			/*
+			 * Child probes are not on the kprobe hash list, so
+			 * the above loop can not find them. If a child probe
+			 * is allocated in the module's memory, it will become
+			 * a dangling pointer after the module is freed.
+			 */
+			if (kprobe_aggrprobe(p)) {
+				struct kprobe *kp, *kptmp;
+
+				list_for_each_entry_safe(kp, kptmp, &p->list, list) {
+					if (within_module_init((unsigned long)kp, mod) ||
+					    (checkcore &&
+					     within_module_core((unsigned long)kp, mod))) {
+						kp->flags |= KPROBE_FLAG_GONE;
+						list_del_rcu(&kp->list);
+					}
+				}
+			}
+		}
 	}
 	if (val == MODULE_STATE_GOING)
 		remove_module_kprobe_blacklist(mod);
-- 
2.20.1


             reply	other threads:[~2026-04-29  3:30 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-29  3:29 Shijia Hu [this message]
2026-04-29  8:40 ` [PATCH] kprobes: Remove dead child probes from aggrprobe list on module unload Masami Hiramatsu
2026-04-29 15:56   ` Steven Rostedt
2026-04-30  1:39     ` Shijia Hu

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=20260429032919.208790-1-hushijia1@uniontech.com \
    --to=hushijia1@uniontech.com \
    --cc=akpm@linux-foundation.org \
    --cc=ananth@in.ibm.com \
    --cc=davem@davemloft.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=mhiramat@kernel.org \
    --cc=naveen@kernel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox