Re: [PATCHv4 bpf-next 10/12] bpf: Disable preemption in __bpf_get_stack
Andrii Nakryiko <[email protected]> Wed, 5 Aug 2026 11:34:58 -0700
| Newsgroups | org.kernel.vger.bpf,org.kernel.vger.stable |
|---|---|
| Message-ID | <CAEf4BzZQXq5CNSkD4-gMOhs5+GU9Sf9Q7sHvLu1BkpqDTQHa5g@mail.gmail.com> |
On Wed, Aug 5, 2026 at 2:30 AM Jiri Olsa <[email protected]> wrote: > > From: Daniel Borkmann <[email protected]> > > get_perf_callchain() returns a per-CPU perf_callchain_entry buffer and > releases its recursion slot via put_callchain_entry() before returning, > so nothing keeps the entry reserved while __bpf_get_stack() consumes > it below. > > A preemptible BPF program (e.g. a non-sleepable raw tracepoint program > on a PREEMPT kernel, which runs under migrate_disable() but not > preempt_disable()) can be scheduled out between obtaining the entry > and the copy. Another task scheduled on the same CPU then reuses the > same per-CPU buffer and overwrites trace->nr with a larger value. > copy_len is then computed from the inflated trace->nr and can exceed > the caller's buffer, causing an out-of-bounds write in the memcpy() > and in the build_id path. > > The rcu_read_lock() taken here alone does not prevent this. It is > only taken on the may_fault path, and under CONFIG_PREEMPT_RCU it does > not disable preemption; it merely keeps perf's callchain buffer array > alive (freed via call_rcu()) and does nothing to stop another task > from reusing the entry. > > Disable preemption around obtaining the callchain entry and copying > it into the caller's buffer, so the entry cannot be reused underneath > us and trace->nr stays bounded by max_depth. Build ID resolution may > fault and is therefore deferred until after preemption is re-enabled; > by then the instruction pointers have already been copied into buf, > so it operates only on that private copy. Note, preempt_disable() also > subsumes the buffer-lifetime guarantee the rcu_read_lock() provided, > since a preempt-disabled section is an RCU read-side critical section > for the callchain buffers' call_rcu() reclaim. > > Cc: [email protected] > Fixes: c195651e565a ("bpf: add bpf_get_stack helper") > Reported-by: Tao Chen <[email protected]> > Closes: https://lore.kernel.org/bpf/[email protected]/ > Reported-by: STAR Labs SG <[email protected]> > Signed-off-by: Daniel Borkmann <[email protected]> > [ changed Fixes: commit ] > Signed-off-by: Jiri Olsa <[email protected]> > --- > kernel/bpf/stackmap.c | 3 +++ > 1 file changed, 3 insertions(+) > > diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c > index eabeaef31b63..789fe35b893a 100644 > --- a/kernel/bpf/stackmap.c > +++ b/kernel/bpf/stackmap.c > @@ -817,6 +817,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, > > if (may_fault) > rcu_read_lock(); /* need RCU for perf's callchain below */ > + preempt_disable(); nit: asymmetrical to preempt_enable, I'll move it to before rcu_read_lock, so we can have proper nesting > > if (kernel && task) { > trace = get_callchain_entry_for_task(task, max_depth); > @@ -828,6 +829,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, > if (unlikely(!trace) || trace->nr < skip) { > if (may_fault) > rcu_read_unlock(); > + preempt_enable(); > goto err_fault; > } > > @@ -836,6 +838,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, > /* trace should not be dereferenced after this point */ > if (may_fault) > rcu_read_unlock(); > + preempt_enable(); > > return callchain_finalize(buf, size, trace_nr, elem_size, flags, may_fault); > > -- > 2.54.0 >