All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Dapeng Mi" <dapeng1.mi@linux.intel.com>
Cc: linux-perf-users@vger.kernel.org, bpf@vger.kernel.org
Subject: Re: [Patch v4 5/5] pef/x86/intel: Always snapshot live hardware LBR state for BPF path
Date: Wed, 21 Oct 2026 06:05:17 +0000	[thread overview]
Message-ID: <sashiko-outbox-179823@kernel.org> (raw)
In-Reply-To: <20261021050711.4043703-6-dapeng1.mi@linux.intel.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] perf/x86/intel: LBR state corruption in __intel_pmu_snapshot_branch_stack()
- [High] perf/x86/intel: __intel_pmu_snapshot_branch_stack() enables PMU in PMI
- [Medium] perf/x86/intel: unfrozen LBRs and PMU re-enable in PMI branch snapshot
- [Medium] perf/x86: NMI race on lbr_snapshot in __intel_pmu_snapshot_branch_stack()
- [Medium] perf/x86/intel: unfiltered LBR branch counter bits in perf_branch_entry.reserved
--

commit 4d2b360d61a8a8f6825b3cd919182ee99dbe2d76
Author: Dapeng Mi <dapeng1.mi@linux.intel.com>

pef/x86/intel: Always snapshot live hardware LBR state for BPF path

This commit updates __intel_pmu_snapshot_branch_stack() to always read live
hardware LBR registers for the BPF snapshot path even when PEBS LBR users
are active. To avoid clobbering cached PEBS LBR data during a PMI, it saves
and restores cpuc->lbr_stack, cpuc->lbr_entries, and cpuc->lbr_counters
around intel_pmu_lbr_read() using a per-CPU buffer, and moves the
cpuc->lbr_users == cpuc->lbr_pebs_users check into intel_pmu_handle_irq().

> diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c
> index 5dfb9e9156e88..07d1a8a7845b2 100644
> --- a/arch/x86/events/intel/core.c
> +++ b/arch/x86/events/intel/core.c
> @@ -2815,16 +2815,50 @@ static void intel_pmu_enable_all(int added)
>  	__intel_pmu_enable_all(added, false);
>  }
>  
> +struct lbr_snapshot {
> +	struct perf_branch_stack stack;
> +	struct perf_branch_entry entries[MAX_LBR_ENTRIES];
> +	u64 counters[MAX_LBR_ENTRIES];
> +};
> +static DEFINE_PER_CPU(struct lbr_snapshot, lbr_snapshot);

[Severity: Medium]
Can an NMI race on the per-CPU lbr_snapshot buffer when
__intel_pmu_snapshot_branch_stack() is called in non-NMI context?

In arch/x86/events/intel/core.c, intel_pmu_snapshot_branch_stack() and
intel_pmu_snapshot_arch_branch_stack() call local_irq_save(flags) and
__intel_pmu_disable_all(false) before invoking
__intel_pmu_snapshot_branch_stack(). However, local_irq_save() only masks
normal interrupts, and an in-flight PMI or another NMI can still arrive
after __intel_pmu_disable_all() due to NMI delivery latency.

When cpuc->lbr_pebs_users > 0 and a non-NMI BPF program (such as an
fentry/fexit trampoline via __bpf_prog_enter_recur() or a raw_tracepoint,
where bpf_prog_active is not incremented) calls:

  bpf_get_branch_snapshot()
    -> intel_pmu_snapshot_branch_stack()
      -> __intel_pmu_snapshot_branch_stack()

an arriving PMI can still invoke bpf_overflow_handler() ->
bpf_get_branch_snapshot() -> __intel_pmu_snapshot_branch_stack() on the
same CPU, overwriting this_cpu_ptr(&lbr_snapshot) while the outer call is
between its save and restore steps. Even if the PMI does not call
bpf_get_branch_snapshot(), intel_pmu_handle_irq() updates cpuc->lbr_stack
and cpuc->lbr_entries while the interrupted snapshot is saving or
restoring them.

Since cpuc->lbr_stack only holds live sample data awaiting output during a
PMI, should the save and restore of cpuc->lbr_stack be restricted to NMI
context or otherwise protected against NMI reentrancy?

> +
>  static noinline int
>  __intel_pmu_snapshot_branch_stack(struct perf_branch_entry *entries,
>  				  unsigned int cnt, unsigned long flags)
>  {
>  	struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
> +	struct lbr_snapshot *snapshot = this_cpu_ptr(&lbr_snapshot);
> +
> +	/*
> +	 * This may be called from bpf_event_handler() in a PMI context. If
> +	 * a PEBS LBR user is active, the PEBS sample record has already updated
> +	 * cpuc->lbr_stack. Reading the LBR registers here would overwrite that
> +	 * cached PEBS LBR stack, so save it first and restore it afterward. This
> +	 * keeps the cached LBR data available for later output to userspace in
> +	 * the normal PEBS record path.
> +	 */
> +	if (cpuc->lbr_pebs_users) {
> +		memcpy(&snapshot->stack, &cpuc->lbr_stack,
> +		       sizeof(struct perf_branch_stack));
> +		memcpy(snapshot->entries, cpuc->lbr_entries,
> +		       sizeof(struct perf_branch_entry) * cpuc->lbr_stack.nr);
> +		memcpy(snapshot->counters, cpuc->lbr_counters,
> +		       sizeof(u64) * cpuc->lbr_stack.nr);
> +	}

[Severity: Critical]
What happens in __intel_pmu_snapshot_branch_stack() when cpuc->lbr_users
is non-zero, but cpuc->lbr_pebs_users is 0?

In arch/x86/events/intel/lbr.c:intel_pmu_lbr_add(), cpuc->lbr_pebs_users
is only incremented when x86_pmu.intel_cap.pebs_baseline is set and
event->attr.precise_ip > 0:

	if (x86_pmu.intel_cap.pebs_baseline && event->attr.precise_ip > 0)
		cpuc->lbr_pebs_users++;

When non-PEBS hardware LBR events (precise_ip == 0), legacy PEBS LBR
events (pre-Ice Lake where pebs_baseline == 0), or Architectural PEBS LBR
events (arch/x86/events/intel/ds.c:setup_arch_pebs_sample_data(), where
pebs_baseline == 0) are active, cpuc->lbr_users > 0 while
cpuc->lbr_pebs_users == 0.

In those paths, handle_pmi_common(), setup_pebs_fixed_sample_data(), and
setup_arch_pebs_sample_data() call intel_pmu_lbr_save_brstack() before
perf_event_overflow() -> __perf_event_overflow() ->
bpf_overflow_handler():

	if (has_branch_stack(event))
		intel_pmu_lbr_save_brstack(&data, cpuc, event);
	...
	perf_event_overflow(event, &data, regs);

Inside include/linux/perf_event.h:perf_sample_save_brstack(), brs
(&cpuc->lbr_stack) is clamped in place and used to precompute
data->dyn_size:

	brs->nr = min_t(u16, event->attr.sample_max_stack, brs->nr);
	size += brs->nr * sizeof(struct perf_branch_entry);
	if (brs_cntr)
		size += brs->nr * sizeof(u64);
	data->br_stack = brs;
	data->br_stack_cntr = brs_cntr;
	data->dyn_size += size;
	data->sample_flags |= PERF_SAMPLE_BRANCH_STACK;

If bpf_overflow_handler() then calls bpf_get_branch_snapshot() ->
__intel_pmu_snapshot_branch_stack() with cpuc->lbr_pebs_users == 0, the
save and restore of cpuc->lbr_stack, cpuc->lbr_entries, and
cpuc->lbr_counters are skipped, and intel_pmu_lbr_read() overwrites them
in place:

1. In kernel/events/core.c:perf_output_sample(), data->br_stack still
   points to &cpuc->lbr_stack. Because intel_pmu_lbr_read() resets
   cpuc->lbr_stack.nr up to x86_pmu.lbr_nr, undoing the sample_max_stack
   clamp after data->dyn_size was already computed, perf_output_sample()
   can copy more perf_branch_entry (and counter) elements than
   perf_output_begin() reserved in the ring buffer.

2. The frozen LBR entries captured at PMI entry (or from the Arch PEBS
   record) in cpuc->lbr_entries are overwritten by unfrozen post-ack LBR
   registers containing PMI handler branches.

3. For branch counter groups, intel_pmu_lbr_counters_reorder() has already
   extracted counter bits from cpuc->lbr_entries[i].reserved into
   cpuc->lbr_counters[i] and cleared .reserved; calling
   intel_pmu_lbr_read() -> intel_pmu_store_lbr() writes raw counter bits
   back into cpuc->lbr_entries[i].reserved and desynchronizes
   cpuc->lbr_counters[].

Should the save and restore in __intel_pmu_snapshot_branch_stack() be
gated on cpuc->lbr_users instead of cpuc->lbr_pebs_users (and save/restore
based on the pre-clamp entry count or full array when needed)?

>  
>  	intel_pmu_lbr_read();
>  	cnt = min_t(unsigned int, cnt, cpuc->lbr_stack.nr);
> -
>  	memcpy(entries, cpuc->lbr_entries, sizeof(struct perf_branch_entry) * cnt);

[Severity: Medium]
On platforms with Architectural LBR and branch counter logging, does
copying cpuc->lbr_entries directly after intel_pmu_lbr_read() leak raw
branch counter bits in entries[i].reserved to BPF programs?

When intel_pmu_snapshot_arch_branch_stack() calls
__intel_pmu_snapshot_branch_stack(), intel_pmu_lbr_read() invokes
intel_pmu_arch_lbr_read() or intel_pmu_arch_lbr_read_xsave(), which calls
arch/x86/events/intel/lbr.c:intel_pmu_store_lbr():

	/*
	 * Leverage the reserved field of
	 * cpuc->lbr_entries[i] to temporarily store the
	 * branch counters information. The later code will
	 * decide what content can be disclosed to the perf
	 * tool. Pleae see intel_pmu_lbr_counters_reorder().
	 */
	.reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) &
		    LBR_INFO_BR_CNTR_FULL_MASK,

Normally intel_pmu_lbr_counters_reorder() filters those bits and clears
cpuc->lbr_entries[i].reserved to 0 before the entries are exposed. Since
__intel_pmu_snapshot_branch_stack() copies cpuc->lbr_entries directly into
entries without calling intel_pmu_lbr_counters_reorder(), won't the raw
hardware counter bits remain in the UAPI perf_branch_entry.reserved field
returned by bpf_get_branch_snapshot()?

> +
> +	if (cpuc->lbr_pebs_users) {
> +		memcpy(&cpuc->lbr_stack, &snapshot->stack,
> +		       sizeof(struct perf_branch_stack));
> +		memcpy(cpuc->lbr_entries, snapshot->entries,
> +		       sizeof(struct perf_branch_entry) * cpuc->lbr_stack.nr);
> +		memcpy(cpuc->lbr_counters, snapshot->counters,
> +		       sizeof(u64) * cpuc->lbr_stack.nr);
> +	}
> +
>  	intel_pmu_enable_all(0);

[Severity: High]
Is it safe for __intel_pmu_snapshot_branch_stack() to unconditionally call
intel_pmu_enable_all(0) when invoked from bpf_overflow_handler() in a PMI?

In arch/x86/events/intel/core.c:intel_pmu_handle_irq(), the PMI handler
saves cpuc->enabled, sets cpuc->enabled = 0, and disables the PMU in
hardware:

	pmu_enabled = cpuc->enabled;
	...
	cpuc->enabled = 0;
	__intel_pmu_disable_all(true);

The PMI handler expects the PMU, PEBS, BTS, and LBRs to remain disabled in
hardware while handle_pmi_common() and __intel_pmu_pebs_last_event() drain
PEBS records and reprogram counter periods, only restoring the hardware
state at the done label if pmu_enabled was set:

	/* Only restore PMU state when it's active. See x86_pmu_disable(). */
	cpuc->enabled = pmu_enabled;
	if (pmu_enabled)
		__intel_pmu_enable_all(0, true);

When a BPF program runs via perf_event_overflow() ->
bpf_overflow_handler() during a PMI and calls bpf_get_branch_snapshot() ->
__intel_pmu_snapshot_branch_stack(), calling intel_pmu_enable_all(0)
invokes x86_pmu_pebs_enable_all() and __intel_pmu_enable_all(0, false)
while cpuc->enabled is 0. This re-enables MSR_IA32_PEBS_ENABLE, LBRs, BTS,
and MSR_CORE_PERF_GLOBAL_CTRL before __intel_pmu_pebs_last_event() and
handle_pmi_common() finish reprogramming counter periods, and leaves the
hardware PMU enabled after the PMI exits if the PMI interrupted an
x86_pmu_disable() section where pmu_enabled was 0.

Should __intel_pmu_snapshot_branch_stack() avoid calling
intel_pmu_enable_all(0) when cpuc->enabled is 0?

>  	local_irq_restore(flags);
>  	return cnt;
> @@ -4022,7 +4056,12 @@ static int intel_pmu_handle_irq(struct pt_regs *regs)
>  
>  	loops = 0;
>  again:
> -	intel_pmu_lbr_read();
> +	/*
> +	 * No need to read the LBR stack when all LBR users are PEBS events,
> +	 * since the PEBS handler updates it.
> +	 */
> +	if (cpuc->lbr_users != cpuc->lbr_pebs_users)
> +		intel_pmu_lbr_read();
>  	intel_pmu_ack_status(status);

[Severity: Medium]
When cpuc->lbr_users == cpuc->lbr_pebs_users and a BPF program calls
bpf_get_branch_snapshot() from bpf_overflow_handler() during a PMI, haven't
the hardware LBR registers already been unfrozen by
intel_pmu_ack_status(status)?

In arch/x86/events/intel/core.c:intel_pmu_handle_irq(),
__intel_pmu_disable_all(true) clears MSR_CORE_PERF_GLOBAL_CTRL but does
not clear LBR enable bits in MSR_IA32_DEBUGCTLMSR or MSR_ARCH_LBR_CTL,
relying on GLOBAL_STATUS_LBRS_FROZEN to keep the LBR stack frozen.

As noted in arch/x86/events/intel/lbr.c:__intel_pmu_lbr_enable():

	/*
	 * No need to unfreeze manually, as v4 can do that as part
	 * of the GLOBAL_STATUS ack.
	 */
	if (pmi && x86_pmu.version >= 4)
		return;

On PMU v4+ platforms (which include all pebs_baseline platforms), calling
intel_pmu_ack_status(status) clears GLOBAL_STATUS_LBRS_FROZEN and
immediately unfreezes the hardware LBRs. By the time handle_pmi_common() ->
x86_pmu_drain_pebs() -> __intel_pmu_pebs_last_event() sets up the PEBS
sample and invokes bpf_overflow_handler() ->
__intel_pmu_snapshot_branch_stack(), the unfrozen hardware LBR MSRs have
been recording branches inside the PMI handler itself (and
__intel_pmu_snapshot_branch_stack() then also calls intel_pmu_enable_all(0)
before the PMI finishes).

Can __intel_pmu_snapshot_branch_stack() still obtain an unpolluted hardware
LBR snapshot in this PMI path after intel_pmu_ack_status(status) has
unfrozen the LBRs?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021050711.4043703-1-dapeng1.mi@linux.intel.com?part=5

  reply	other threads:[~2026-10-21  6:05 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21  5:07 [Patch v4 0/5] perf/x86: Limit branch snapshots to valid entries Dapeng Mi
2026-10-21  5:07 ` [Patch v4 1/5] perf/x86: Disable LBR if x86_pmu.lbr_nr exceeds MAX_LBR_ENTRIES Dapeng Mi
2026-10-21  5:42   ` sashiko-bot
2026-10-21  6:16     ` Mi, Dapeng
2026-10-21  5:07 ` [Patch v4 2/5] perf/x86/amd/lbr: Limit branch snapshots to valid entries Dapeng Mi
2026-10-21  5:31   ` sashiko-bot
2026-10-21  5:07 ` [Patch v4 3/5] perf/x86/intel/lbr: Reset the cached LBR stack in intel_pmu_lbr_reset() Dapeng Mi
2026-10-21  5:44   ` sashiko-bot
2026-10-21  5:07 ` [Patch v4 4/5] perf/x86/intel/lbr: Limit branch snapshots to valid entries Dapeng Mi
2026-10-21  5:37   ` sashiko-bot
2026-10-21  5:07 ` [Patch v4 5/5] pef/x86/intel: Always snapshot live hardware LBR state for BPF path Dapeng Mi
2026-10-21  6:05   ` sashiko-bot [this message]
2026-10-21  6:53     ` Mi, Dapeng
2026-10-22  6:19     ` Mi, Dapeng
2026-10-21  6:59   ` Mi, Dapeng

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-179823@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=dapeng1.mi@linux.intel.com \
    --cc=linux-perf-users@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.