All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Artem Dinaburg" <artem@trailofbits.com>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH 6.6.y] bpf: Fix stackmap overflow check in __bpf_get_stackid()
Date: Wed, 21 Oct 2026 17:28:38 +0000	[thread overview]
Message-ID: <sashiko-outbox-180524@kernel.org> (raw)
In-Reply-To: <20261021171625.206-1-artem@trailofbits.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] bpf: integer underflow and OOB write in __bpf_get_stackid()
--

commit 8a7616c005136eb52127894543c589eeca649242
Author: Arnaud Lecomte <contact@arnaud-lcm.com>

bpf: Fix stackmap overflow check in __bpf_get_stackid()

This commit bounds trace_nr in __bpf_get_stackid() to prevent a slab
out-of-bounds write into a stack map bucket when a perf callchain contains
more entries than the bucket can hold. It also restores trace->nr after
both branches of bpf_get_stackid_pe().

Link: https://lore.kernel.org/bpf/20251025192941.1500-1-contact@arnaud-lcm.com

> diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c
> index 458bb80b14d57..60ecf3e82d97b 100644
> --- a/kernel/bpf/stackmap.c
> +++ b/kernel/bpf/stackmap.c
> @@ -213,7 +213,8 @@ static long __bpf_get_stackid(struct bpf_map *map,
>  	struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
>  	struct stack_map_bucket *bucket, *new_bucket, *old_bucket;
>  	u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
> -	u32 hash, id, trace_nr, trace_len;
> +	u32 hash, id, trace_nr, trace_len, max_depth;
> +	u32 curr_sysctl_max_stack;
>  	bool user = flags & BPF_F_USER_STACK;
>  	u64 *ips;
>  	bool hash_matches;
> @@ -222,7 +223,12 @@ static long __bpf_get_stackid(struct bpf_map *map,
>  		/* skipping more than usable stack trace */
>  		return -EFAULT;
>  
> -	trace_nr = trace->nr - skip;
> +	curr_sysctl_max_stack = READ_ONCE(sysctl_perf_event_max_stack);
> +	max_depth = map->value_size / stack_map_data_size(map);
> +	max_depth += skip;
> +	if (max_depth > curr_sysctl_max_stack)
> +		max_depth = curr_sysctl_max_stack;
> +	trace_nr = min_t(u32, trace->nr - skip, max_depth - skip);

[Severity: Critical]
Can max_depth - skip underflow here and bypass the bucket size check when
__bpf_get_stackid() is called from bpf_get_stackid_pe()?

When a perf event has PERF_SAMPLE_CALLCHAIN enabled, ctx->data->callchain is
populated by perf_callchain() -> get_perf_callchain() with add_mark = true.
In include/linux/perf_event.h:perf_callchain_store_context(), context markers
such as PERF_CONTEXT_KERNEL and PERF_CONTEXT_USER are appended to entry->ip
without incrementing ctx->nr:

include/linux/perf_event.h:perf_callchain_store_context() {
	if (ctx->contexts < sysctl_perf_event_max_contexts_per_stack) {
		struct perf_callchain_entry *entry = ctx->entry;
		entry->ip[entry->nr++] = ip;
		++ctx->contexts;
		return 0;
	} else {
		return -1;
	}
}

Because ctx->nr is bounded by sysctl_perf_event_max_stack while context
markers are counted separately up to sysctl_perf_event_max_contexts_per_stack,
trace->nr can legitimately exceed curr_sysctl_max_stack (for example, up to
curr_sysctl_max_stack + 2 on x86_64 or curr_sysctl_max_stack + 8 on powerpc
when interrupt or signal frame markers are present).

In kernel/bpf/stackmap.c:bpf_get_stackid_pe(), skip comes from
flags & BPF_F_SKIP_FIELD_MASK (up to 255), and for BPF_F_USER_STACK it also
adds nr_kernel:

kernel/bpf/stackmap.c:bpf_get_stackid_pe() {
...
	} else { /* user */
		u64 skip = flags & BPF_F_SKIP_FIELD_MASK;

		skip += nr_kernel;
		if (skip > BPF_F_SKIP_FIELD_MASK)
			return -EFAULT;

		flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip;
		ret = __bpf_get_stackid(map, trace, flags);
	}
...
}

If curr_sysctl_max_stack < skip < trace->nr, the trace->nr <= skip check in
__bpf_get_stackid() passes, and clamping max_depth to curr_sysctl_max_stack
makes max_depth smaller than skip. The u32 expression max_depth - skip then
wraps around to a large unsigned value, so min_t() selects trace->nr - skip
without clamping it to map->value_size / stack_map_data_size(map).

When trace->nr - skip is larger than the number of elements that fit in
new_bucket->data (for example, on a small stack map with a 1-entry bucket),
wouldn't stack_map_get_build_id_offset() or memcpy(new_bucket->data, ips,
trace_len) in __bpf_get_stackid() still write past the end of
new_bucket->data, and subsequently cause bpf_stackmap_copy() to overflow
value in memcpy() and underflow map->value_size - trace_len in memset()?

kernel/bpf/stackmap.c:__bpf_get_stackid() {
...
		new_bucket = (struct stack_map_bucket *)
			pcpu_freelist_pop(&smap->freelist);
		if (unlikely(!new_bucket))
			return -ENOMEM;
		memcpy(new_bucket->data, ips, trace_len);
	}
...
}

kernel/bpf/stackmap.c:bpf_stackmap_copy() {
...
	trace_len = bucket->nr * stack_map_data_size(map);
	memcpy(value, bucket->data, trace_len);
	memset(value + trace_len, 0, map->value_size - trace_len);
...
}

Also, when skip == curr_sysctl_max_stack and trace->nr > skip, max_depth -
skip evaluates to 0, which records an empty bucket with new_bucket->nr = 0;
and when skip < curr_sysctl_max_stack < max_depth, clamping max_depth to
curr_sysctl_max_stack before subtracting skip truncates frames that would
otherwise fit in the bucket.

Since trace is already unwound before __bpf_get_stackid() is called, should
__bpf_get_stackid() clamp trace->nr - skip directly against the bucket
capacity map->value_size / stack_map_data_size(map), matching how
__bpf_get_stack() clamps trace_nr against num_elem?

kernel/bpf/stackmap.c:__bpf_get_stack() {
...
	trace_nr = trace->nr - skip;
	trace_nr = (trace_nr <= num_elem) ? trace_nr : num_elem;
...
}

>  	trace_len = trace_nr * sizeof(u64);
>  	ips = trace->ip + skip;
>  	hash = jhash2((u32 *)ips, trace_len / sizeof(u32), 0);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021171625.206-1-artem@trailofbits.com?part=1

  reply	other threads:[~2026-10-21 17:28 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 17:16 [PATCH 6.6.y] bpf: Fix stackmap overflow check in __bpf_get_stackid() Artem Dinaburg
2026-10-21 17:28 ` sashiko-bot [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-10-16  2:32 Artem Dinaburg
2026-10-16  2:39 ` sashiko-bot

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-180524@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=artem@trailofbits.com \
    --cc=bpf@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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.