Re: [Patch v3 8/8] perf/x86/intel: Prevent drain_pebs() reentry

From: Mi, Dapeng

Date: Mon Aug 10 2026 - 21:39:34 EST



On 8/10/2026 9:01 PM, Peter Zijlstra wrote:
> On Fri, Jul 17, 2026 at 04:03:42PM +0800, Dapeng Mi wrote:
>> The PEBS buffer is shared by all events on a CPU, so drain_pebs() must
>> not run concurrently. If it is reentered, one instance may observe stale
>> buffer state and potentially access out-of-bound memory.
>>
>> Most invocations happen in NMI context, which naturally prevents reentry.
>> However, drain_pebs() is also reachable from process context via
>> intel_pmu_drain_pebs_buffer().
>>
>> In those paths, the PMU is often already disabled, but not guaranteed.
>> For example, __intel_pmu_pebs_disable() only disables the target counter,
>> so other active counters can still raise a PMI and interrupt an in-flight
>> drain_pebs().
>>
>> Introduce __intel_pmu_quiesce() and __intel_pmu_resume() helpers and
>> use them in intel_pmu_drain_pebs_buffer() to disable the full PMU
>> around the drain_pebs() call, preventing reentry.
>>
> It is not at all clear to me where the exact recursion happens. (The
> word you're looking for was recursion, not concurrent).

Yes, the word "concurrently" is not accurate, reentry is the more accurate
word.

Currently drain_pebs() would be called in two places, one is the in the PMI
handler, like handle_pmi_common(). The other place is
intel_pmu_drain_pebs_buffer() which is from process context.

So when intel_pmu_drain_pebs_buffer() is calling drain_pebs(), if there is
an active PEBS event triggering PMI, it would interrupt current in-flight
drain_pebs() and lead to drain_pebs() reentry.

The good news is the global pmu has been disabled in most places before
calling intel_pmu_drain_pebs_buffer(), so no new PMI can be triggered to
interrupt current running drain_pebs() helper, but not all places does so,
like __intel_pmu_pebs_disable() where only the target counter has been
disabled instead of the whole PMU. So it's still possible tjat another
active PEBS event triggers PMI and interrupts current running drain_pebs().

Take the intel_pmu_drain_arch_pebs() as an example,

    base = cpuc->pebs_vaddr;
    top = cpuc->pebs_vaddr + (index.wr << ARCH_PEBS_INDEX_WR_SHIFT);
    ------> interrupted here ...  


    index.wr = 0;
    index.full = 0;
    index.en = 1;
    if (cpuc->n_pebs == cpuc->n_large_pebs)
        index.thresh = ARCH_PEBS_THRESH_MULTI;
    else
        index.thresh = ARCH_PEBS_THRESH_SINGLE;
    wrmsrq(MSR_IA32_PEBS_INDEX, index.whole);

Assume the drain_pebs() is interrupted just after reading the top value by
a new PEBS PMI and the PMI handler would drain all the PEBS buffer. When
the PMI returns and the original drian_pebs() continues to execute but it
doesn't know the PEBS buffer has been cleared and may access some stale
data and lead to some unexpected errors.