Re: [PATCH V5] perf/x86: Reset the dirty counter to prevent the leak for an RDPMC task

From: Peter Zijlstra
Date: Wed Apr 21 2021 - 04:11:20 EST


On Tue, Apr 20, 2021 at 03:30:42PM -0700, kan.liang@xxxxxxxxxxxxxxx wrote:
> static void x86_pmu_event_mapped(struct perf_event *event, struct mm_struct *mm)
> {
> + unsigned long flags;
> +
> if (!(event->hw.flags & PERF_X86_EVENT_RDPMC_ALLOWED))
> return;
>
> /*
> + * Enable sched_task() for the RDPMC task,
> + * and clear the existing dirty counters.
> + */
> + if (x86_pmu.sched_task && event->hw.target) {
> + local_irq_save(flags);
> + perf_sched_cb_inc(event->ctx->pmu);
> + x86_pmu_clear_dirty_counters();
> + local_irq_restore(flags);
> + }
> +
> + /*
> * This function relies on not being called concurrently in two
> * tasks in the same mm. Otherwise one task could observe
> * perf_rdpmc_allowed > 1 and return all the way back to
> @@ -2327,10 +2367,17 @@ static void x86_pmu_event_mapped(struct perf_event *event, struct mm_struct *mm)
>
> static void x86_pmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
> {
> + unsigned long flags;
>
> if (!(event->hw.flags & PERF_X86_EVENT_RDPMC_ALLOWED))
> return;
>
> + if (x86_pmu.sched_task && event->hw.target) {
> + local_irq_save(flags);
> + perf_sched_cb_dec(event->ctx->pmu);
> + local_irq_restore(flags);
> + }
> +
> if (atomic_dec_and_test(&mm->context.perf_rdpmc_allowed))
> on_each_cpu_mask(mm_cpumask(mm), cr4_update_pce, NULL, 1);
> }

I don't understand how this can possibly be correct. Both
perf_sched_cb_{inc,dec} modify strict per-cpu state, but the mapped
functions happen on whatever random CPU of the moment whenever the task
memory map changes.

Suppose we mmap() on CPU0 and then exit on CPU1. Suppose the task does
mmap() on CPU0 but then creates threads and runs on CPU1-4 concurrently
before existing on CPU5.

Could be I'm not seeing it due to having a snot-brain, please explain.