From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Cc: daniel@iogearbox.net, andrii@kernel.org, eddyz87@gmail.com,
memxor@gmail.com, oleg@redhat.com, peterz@infradead.org,
mhiramat@kernel.org, jolsa@kernel.org, akpm@linux-foundation.org,
liam@infradead.org, ljs@kernel.org, surenb@google.com,
david@kernel.org, vbabka@kernel.org, jannh@google.com,
hughd@google.com, kees@kernel.org, mingo@redhat.com,
tglx@kernel.org, x86@kernel.org,
linux-trace-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-perf-users@vger.kernel.org
Subject: Re: [PATCH bpf-next 8/9] uprobes: Don't leak the breakpoints if the unregistering task is killed
Date: Thu, 22 Oct 2026 02:20:23 +0000 [thread overview]
Message-ID: <sashiko-outbox-180864@kernel.org> (raw)
In-Reply-To: <20261022015712.1297331-9-alexei.starovoitov@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] uprobes: double ref_ctr decrement and breakpoint leak in unregister_all_vmas()
--
commit 02db19b4afb60ef4b8ca6aaa98c8fe074f02d199
Author: Alexei Starovoitov <ast@kernel.org>
uprobes: Don't leak the breakpoints if the unregistering task is killed
This commit offloads breakpoint removal in uprobe_unregister_nosync() and
uprobe_apply() to an unbound workqueue item when the file is mapped by user
processes, preventing a fatal signal on the unregistering task from failing
get_user_pages_remote() with -EINTR and leaking breakpoints.
> diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
> index 92ccb730278f3..361b52c9c3bcf 100644
> --- a/kernel/events/uprobes.c
> +++ b/kernel/events/uprobes.c
> @@ -1426,6 +1426,68 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new)
> return walk.err;
> }
>
> +struct unregister_work {
> + struct work_struct work;
> + struct uprobe *uprobe;
> + int err;
> +};
> +
> +static void unregister_work_fn(struct work_struct *work)
> +{
> + struct unregister_work *uw = container_of(work, struct unregister_work, work);
> +
> + uw->err = register_for_each_vma(uw->uprobe, NULL);
> +}
> +
> +/*
> + * Remove the breakpoints. If current is killed, get_user_pages_remote() fails
> + * with -EINTR from then on: the breakpoints which are left stay for good, and
> + * the removal which was interrupted can be left half-way, with the reference
> + * counter decremented but the breakpoint still there. So let a kworker do the
> + * job, it has no signals. It takes the locks we would take ourselves.
> + * (A fork() of an mm with busy mmap_lock can still do one removal in a task
> + * which is being killed, see register_one_mm().)
> + *
> + * A kernel thread can do it itself. So can anybody if nobody maps the file:
> + * then there is no breakpoint, and uprobe_mmap() installs none which we would
> + * have to remove.
> + */
> +static int unregister_all_vmas(struct uprobe *uprobe)
> +{
> + struct address_space *mapping = uprobe->inode->i_mapping;
> + struct unregister_work uw = { .uprobe = uprobe };
> + bool mapped;
> + int err;
> +
> + /*
> + * vma_prepare() takes a vma out of the tree and vma_complete() puts it
> + * back, under i_mmap_lock_write(). Don't mistake that for an empty tree.
> + */
> + i_mmap_lock_read(mapping);
> + mapped = mapping_mapped(mapping);
> + i_mmap_unlock_read(mapping);
> +
> + if (!mapped || (current->flags & PF_KTHREAD)) {
> + err = register_for_each_vma(uprobe, NULL);
> + /*
> + * An error with a fatal signal pending: the file was mapped
> + * meanwhile, and get_user_pages_remote() returned -EINTR before
> + * it could see that there is nothing to remove. Nothing was
> + * written, so it is safe to let the kworker repeat the walk.
> + * (Kernel threads which unregister uprobes don't allow SIGKILL.)
> + */
> + if (likely(!err) || !fatal_signal_pending(current))
> + return err;
> + }
[Severity: Medium]
Can the !mapped path in unregister_all_vmas() still leave ref_ctr decremented
and then decrement it a second time when the kworker repeats the walk?
When !mapped is observed under i_mmap_lock_read(mapping), unregister_all_vmas()
drops the lock and calls register_for_each_vma(uprobe, NULL) in the killable
user task instead of returning 0 directly.
While i_mmap_lock_read(mapping) is dropped, a process can map the file.
uprobe_mmap() runs without uprobe->register_rwsem and installs the breakpoint
if any consumer in filter_chain(uprobe, vma->vm_mm) matches at that moment:
kernel/events/uprobes.c:uprobe_mmap() {
...
list_for_each_entry_safe(uprobe, u, &tmp_list, pending_list) {
if (!fatal_signal_pending(current) &&
filter_chain(uprobe, vma->vm_mm)) {
unsigned long vaddr = offset_to_vaddr(vma, uprobe->offset);
install_breakpoint(uprobe, vma, vaddr);
}
put_uprobe(uprobe);
}
}
Before register_for_each_vma() -> register_one_mm() inspects that mm,
filter_chain(uprobe, mm) can become false without taking
uprobe->register_rwsem. For example, another thread can run
uprobe_perf_close(), which updates the filter before calling uprobe_apply():
kernel/trace/trace_uprobe.c:uprobe_perf_close() {
tu = container_of(tp, struct trace_uprobe, tp);
if (trace_uprobe_filter_remove(tu->tp.event->filter, event))
return 0;
list_for_each_entry(tu, trace_probe_probe_list(tp), tp.list) {
ret = uprobe_apply(tu->uprobe, &tu->consumer, false);
if (ret)
break;
}
...
}
Or the target thread of a per-task perf event in a multithreaded process can
exit and clear tsk->mm in exit_mm().
Then the first register_for_each_vma(uprobe, NULL) call in unregister_all_vmas()
sees !filter_chain(uprobe, mm) and calls remove_breakpoint() -> uprobe_write().
If the anonymous page is COW-shared (for instance, the process forked after
uprobe_mmap()), the initial pass without FOLL_WRITE decrements ref_ctr via
update_ref_ctr(uprobe, mm, -1), gets -EFAULT from __uprobe_write(), and loops
to retry with FOLL_WRITE:
kernel/events/uprobes.c:uprobe_write() {
...
if (do_update_ref_ctr && !ref_ctr_updated && uprobe->ref_ctr_offset) {
ret = update_ref_ctr(uprobe, mm, is_register ? 1 : -1);
if (ret) {
folio_put(folio);
goto out;
}
ref_ctr_updated = 1;
}
...
switch (ret) {
case -EFAULT:
gup_flags |= FOLL_WRITE | FOLL_SPLIT_PMD;
fallthrough;
case -EAGAIN:
goto retry;
default:
break;
}
out:
/* Revert back reference counter if instruction update failed. */
if (do_update_ref_ctr && ret < 0 && ref_ctr_updated)
update_ref_ctr(uprobe, mm, is_register ? -1 : 1);
}
If current now receives SIGKILL, both get_user_pages_remote() at retry and the
revert call in update_ref_ctr(uprobe, mm, 1) fail with -EINTR, leaving ref_ctr
decremented and the breakpoint still installed. When unregister_all_vmas()
falls through to queue uw.work, the kworker runs register_for_each_vma() again
and decrements ref_ctr a second time (or fails with -EINVAL if ref_ctr would
go negative, leaking the uprobe and breakpoint).
If !mapped is true under i_mmap_lock_read(mapping), could unregister_all_vmas()
return 0 immediately instead of calling register_for_each_vma(uprobe, NULL)?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022015712.1297331-1-alexei.starovoitov@gmail.com?part=8
next prev parent reply other threads:[~2026-10-22 2:20 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 1:57 [PATCH bpf-next 0/9] uprobes: Don't stall fork() when mmap_lock is busy Alexei Starovoitov
2026-10-22 1:57 ` [PATCH bpf-next 1/9] mm: Bring back mmap_write_trylock() Alexei Starovoitov
2026-10-22 2:02 ` sashiko-bot
2026-10-22 1:57 ` [PATCH bpf-next 2/9] uprobes: Don't run exit_mmap() under dup_mmap_sem Alexei Starovoitov
2026-10-22 2:04 ` sashiko-bot
2026-10-22 1:57 ` [PATCH bpf-next 3/9] uprobes/x86: Finish an unoptimization which failed half-way Alexei Starovoitov
2026-10-22 2:06 ` sashiko-bot
2026-10-22 2:57 ` Gregory Price (Meta)
2026-10-22 1:57 ` [PATCH bpf-next 4/9] uprobes/x86: Don't WARN if an optimized probe can't be written Alexei Starovoitov
2026-10-22 2:03 ` sashiko-bot
2026-10-22 2:37 ` bot+bpf-ci
2026-10-22 17:56 ` Jann Horn
2026-10-22 19:33 ` Liam R. Howlett
2026-10-22 1:57 ` [PATCH bpf-next 5/9] uprobes: Don't sleep on mmap_lock under dup_mmap_sem Alexei Starovoitov
2026-10-22 2:16 ` sashiko-bot
2026-10-22 2:37 ` bot+bpf-ci
2026-10-22 23:11 ` Andrii Nakryiko
2026-10-22 1:57 ` [PATCH bpf-next 6/9] uprobes: Don't sleep on mmap_lock with dup_mmap_sem held in dup_mmap() Alexei Starovoitov
2026-10-22 2:15 ` sashiko-bot
2026-10-22 18:42 ` Jann Horn
2026-10-22 1:57 ` [PATCH bpf-next 7/9] fork: Let the new task point to its mm before uprobes can find it Alexei Starovoitov
2026-10-22 2:23 ` sashiko-bot
2026-10-22 1:57 ` [PATCH bpf-next 8/9] uprobes: Don't leak the breakpoints if the unregistering task is killed Alexei Starovoitov
2026-10-22 2:20 ` sashiko-bot [this message]
2026-10-22 1:57 ` [PATCH bpf-next 9/9] selftests/bpf: Test uprobes and fork() with busy mmap_lock Alexei Starovoitov
2026-10-22 2:10 ` sashiko-bot
2026-10-22 2:22 ` Alexei Starovoitov
2026-10-22 2:37 ` bot+bpf-ci
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=sashiko-outbox-180864@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=david@kernel.org \
--cc=eddyz87@gmail.com \
--cc=hughd@google.com \
--cc=jannh@google.com \
--cc=jolsa@kernel.org \
--cc=kees@kernel.org \
--cc=liam@infradead.org \
--cc=linux-mm@kvack.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=memxor@gmail.com \
--cc=mhiramat@kernel.org \
--cc=mingo@redhat.com \
--cc=oleg@redhat.com \
--cc=peterz@infradead.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=surenb@google.com \
--cc=tglx@kernel.org \
--cc=vbabka@kernel.org \
--cc=x86@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