Re: [RFC PATCH 05/10] perf/core: Don't unwind the user stack without an mm
From: James Houghton
Date: Tue Oct 27 2026 - 13:46:37 EST
On Tue, Oct 27, 2026 at 4:09 AM Peter Zijlstra <peterz@xxxxxxxxxxxxx> wrote:
>
> On Mon, Oct 26, 2026 at 09:52:21PM +0000, James Houghton wrote:
> > get_perf_callchain() only checks is_user_task(current) when the regs it
> > is given aren't user mode regs. However, BPF's bpf_get_task_stack() (and
> > bpf_get_task_stack_sleepable()) pass task_pt_regs(current) when called on
> > the current task, which are user mode regs for a user task, even if that
> > task has already gone through exit_mm(), e.g. when called from a program
> > attached to exit_files() or to the dying task's final sched_switch.
> >
> > perf_callchain_user() then unwinds the user stack without an mm. That
> > access does not reliably fail: the user half of the address space
> > belongs to whatever mm happens to be lazily loaded, if any, so the
> > unwinder may read another task's memory. This affects e.g. arm64,
> > riscv, loongarch and csky, whose perf_callchain_user() doesn't check
> > current->mm itself. perf events themselves are not affected, as
> > perf_callchain() already clears the user callchain request when
> > current is not a user task with an mm.
> >
> > Check is_user_task(current) regardless of the regs' mode.
> >
> > Fixes: fa28dcb82a38 ("bpf: Introduce helper bpf_get_task_stack()")
> > Signed-off-by: James Houghton <jthoughton@xxxxxxxxxx>
> > ---
> > kernel/events/callchain.c | 12 ++++++++----
> > 1 file changed, 8 insertions(+), 4 deletions(-)
> >
> > diff --git a/kernel/events/callchain.c b/kernel/events/callchain.c
> > index 9d24b6e0c91f..a451ca2a5573 100644
> > --- a/kernel/events/callchain.c
> > +++ b/kernel/events/callchain.c
> > @@ -245,11 +245,15 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user,
> > }
> >
> > if (user && !crosstask) {
> > - if (!user_mode(regs)) {
> > - if (!is_user_task(current))
> > - goto exit_put;
> > + /*
> > + * Even if @regs are user mode regs, e.g. task_pt_regs(current)
> > + * passed in by bpf_get_task_stack(), current may already have
> > + * gone through exit_mm().
> > + */
> > + if (!is_user_task(current))
> > + goto exit_put;
> > + if (!user_mode(regs))
> > regs = task_pt_regs(current);
> > - }
> >
> > if (defer_cookie) {
> > /*
>
> We have perf_event_exit_task() before exit_mm().
>
> Still, it doesn't hurt to be cautious. So no real objection, but perhaps
> adjust the Changelog?
Right, perf events can't get here, since perf_event_exit_task() runs
before exit_mm() (and CPU-wide events go through perf_callchain()'s
is_user_task() check).
The case this fixes is BPF: bpf_get_task_stack() calls
get_perf_callchain() directly with task_pt_regs(current), so a program
attached to e.g. exit_files() or the task's final sched_switch would
unwind the user stack without an mm.
I've added this paragraph to the start of the changelog to make it a
little clearer that the perf event case isn't the interesting one:
get_perf_callchain() only checks is_user_task(current) when the regs it
is given aren't user mode regs. That's fine for perf events: a task's
own events are torn down by perf_event_exit_task() before exit_mm(), and
samples from CPU-wide events go through perf_callchain(), which already
skips the user callchain if current isn't a user task with an mm.
Are you happy with that?
Thanks for the feedback. :)