mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Peter Zijlstra <peterz@infradead.org>
To: Frederic Weisbecker <frederic@kernel.org>
Cc: Ingo Molnar <mingo@redhat.com>,
	LKML <linux-kernel@vger.kernel.org>,
	"Liang, Kan" <kan.liang@linux.intel.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	Alexander Shishkin <alexander.shishkin@linux.intel.com>,
	Arnaldo Carvalho de Melo <acme@kernel.org>,
	Ian Rogers <irogers@google.com>, Jiri Olsa <jolsa@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Namhyung Kim <namhyung@kernel.org>,
	Ravi Bangoria <ravi.bangoria@amd.com>,
	linux-perf-users@vger.kernel.org
Subject: Re: [PATCH 2/4] perf: Fix irq work dereferencing garbage
Date: Fri, 2 May 2025 12:29:18 +0200	[thread overview]
Message-ID: <20250502102918.GW4198@noisy.programming.kicks-ass.net> (raw)
In-Reply-To: <aA9ic6m6WAcmVBAw@pavilion.home>

On Mon, Apr 28, 2025 at 01:11:47PM +0200, Frederic Weisbecker wrote:
> Le Thu, Apr 24, 2025 at 06:30:24PM +0200, Peter Zijlstra a écrit :
> > On Thu, Apr 24, 2025 at 06:11:26PM +0200, Frederic Weisbecker wrote:
> > > @@ -13940,29 +13941,36 @@ perf_event_exit_event(struct perf_event *event,
> > >  		 * Do destroy all inherited groups, we don't care about those
> > >  		 * and being thorough is better.
> > >  		 */
> > > -		detach_flags |= DETACH_GROUP | DETACH_CHILD;
> > > +		prd.detach_flags |= DETACH_GROUP | DETACH_CHILD;
> > >  		mutex_lock(&parent_event->child_mutex);
> > >  	}
> > >  
> > >  	if (revoke)
> > > -		detach_flags |= DETACH_GROUP | DETACH_REVOKE;
> > > +		prd.detach_flags |= DETACH_GROUP | DETACH_REVOKE;
> > >  
> > > -	perf_remove_from_context(event, detach_flags);
> > > +	perf_remove_from_context(event, &prd);
> > 
> > Isn't all this waay to complicated?
> > 
> > That is, to modify state we need both ctx->mutex and ctx->lock, and this
> > is what __perf_remove_from_context() has, but because of this, holding
> > either one of those locks is sufficient to read the state -- it cannot
> > change.
> > 
> > And here we already hold ctx->mutex.
> > 
> > So can't we simply do:
> > 
> > 	old_state = event->attach_state;
> > 	perf_remove_from_context(event, detach_flags);
> > 
> > 	// do whatever with old_state
> 
> Right, the locking scenario is just a bit more complicated.
> Most flags are set on init or with both ctx mutex and lock.
> But:
> 
> _ PERF_ATTACH_CHILD is set instead with parent child_mutex and ctx lock.

Looks trivial to add ctx->mutex to the mix here. Its not like that's a
fast path. But let me go read your patch before deciding if that's
actually needed :-)

> _ PERF_ATTACH_ITRACE is set from pmu::start(). Thus from the event context
>   with just interrupt disabled. It's probably enough to synchronize against
>   initialization and remove_from_context IPIs but perf_event_exit_event() needs
>   some care.

Right, that's a little tricky indeed. As stated, we don't care about the
bit, but the write shouldn't mess things up.

> So we must hold both ctx mutex and child_mutex (although the pmus_srcu thing
> should make that PERF_ATTACH_CHILD thing visible but let's keep things obvious).
> And also have WRITE_ONCE() / READ_ONCE() to take care about PERF_ATTACH_ITRACE,
> which we don't care about anyway.
> 
> Now this looks like this:
> 
> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index 7bcb02ffb93a..7278ca731a55 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c
> @@ -208,7 +208,6 @@ static void perf_ctx_unlock(struct perf_cpu_context *cpuctx,
>  }
>  
>  #define TASK_TOMBSTONE ((void *)-1L)
> -#define EVENT_TOMBSTONE ((void *)-1L)
>  
>  static bool is_kernel_event(struct perf_event *event)
>  {
> @@ -2338,12 +2337,6 @@ static void perf_child_detach(struct perf_event *event)
>  
>  	sync_child_event(event);
>  	list_del_init(&event->child_list);
> -	/*
> -	 * Cannot set to NULL, as that would confuse the situation vs
> -	 * not being a child event. See for example unaccount_event().
> -	 */
> -	event->parent = EVENT_TOMBSTONE;
> -	put_event(parent_event);
>  }
>  
>  static bool is_orphaned_event(struct perf_event *event)
> @@ -5705,7 +5698,7 @@ static void put_event(struct perf_event *event)
>  	_free_event(event);
>  
>  	/* Matches the refcount bump in inherit_event() */
> -	if (parent && parent != EVENT_TOMBSTONE)
> +	if (parent)
>  		put_event(parent);
>  }
>  
> @@ -9998,7 +9991,7 @@ void perf_event_text_poke(const void *addr, const void *old_bytes,
>  
>  void perf_event_itrace_started(struct perf_event *event)
>  {
> -	event->attach_state |= PERF_ATTACH_ITRACE;
> +	WRITE_ONCE(event->attach_state, event->attach_state | PERF_ATTACH_ITRACE);
>  }
>  
>  static void perf_log_itrace_start(struct perf_event *event)
> @@ -13922,10 +13915,7 @@ perf_event_exit_event(struct perf_event *event,
>  {
>  	struct perf_event *parent_event = event->parent;
>  	unsigned long detach_flags = DETACH_EXIT;
> -	bool is_child = !!parent_event;
> -
> -	if (parent_event == EVENT_TOMBSTONE)
> -		parent_event = NULL;
> +	unsigned int attach_state;
>  
>  	if (parent_event) {
>  		/*
> @@ -13942,6 +13932,8 @@ perf_event_exit_event(struct perf_event *event,
>  		 */
>  		detach_flags |= DETACH_GROUP | DETACH_CHILD;
>  		mutex_lock(&parent_event->child_mutex);
> +		/* PERF_ATTACH_ITRACE might be set concurrently */
> +		attach_state = READ_ONCE(event->attach_state);
>  	}
>  
>  	if (revoke)
> @@ -13951,18 +13943,25 @@ perf_event_exit_event(struct perf_event *event,
>  	/*
>  	 * Child events can be freed.
>  	 */
> -	if (is_child) {
> -		if (parent_event) {
> -			mutex_unlock(&parent_event->child_mutex);
> -			/*
> -			 * Kick perf_poll() for is_event_hup();
> -			 */
> -			perf_event_wakeup(parent_event);
> +	if (parent_event) {
> +		mutex_unlock(&parent_event->child_mutex);
> +		/*
> +		 * Kick perf_poll() for is_event_hup();
> +		 */
> +		perf_event_wakeup(parent_event);

Should not this perf_event_wakeup() be inside the next if() as well?
doing anything on parent_event when !ATTACH_CHILD seems dodgy.

> +
> +		/*
> +		 * Match the refcount initialization. Make sure it doesn't happen
> +		 * twice if pmu_detach_event() calls it on an already exited task.
> +		 */
> +		if (attach_state & PERF_ATTACH_CHILD) {
>  			/*
>  			 * pmu_detach_event() will have an extra refcount.
> +			 * perf_pending_task() might have one too.
>  			 */
>  			put_event(event);
>  		}
> +
>  		return;
>  	}

This is a *much* saner patch, thank you!

So the thing I worried about... which is why I chose for the TOMBSTONE
thing, is that this second invocation will now dereference parent_event,
even though we've already released our reference count on it. 

This is essentially a use-after-free.

The thing that makes it work is RCU. And I think we're good, since the
fail case is two perf_event_exit_event() invocations on the same event,
separated by an RCU grace period, and I don't think this can happen.

But its a shame we can't reliably detect that.. Oh well.

  reply	other threads:[~2025-05-02 10:30 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-24 16:11 [PATCH 0/4] perf fixes Frederic Weisbecker
2025-04-24 16:11 ` [PATCH 1/4] perf: Fix failing inherit_event() doing extra refcount decrement on parent Frederic Weisbecker
2025-04-24 16:16   ` Peter Zijlstra
2025-05-08 10:34   ` [tip: perf/core] " tip-bot2 for Frederic Weisbecker
2025-05-08 19:55   ` tip-bot2 for Frederic Weisbecker
2025-04-24 16:11 ` [PATCH 2/4] perf: Fix irq work dereferencing garbage Frederic Weisbecker
2025-04-24 16:30   ` Peter Zijlstra
2025-04-28 11:11     ` Frederic Weisbecker
2025-05-02 10:29       ` Peter Zijlstra [this message]
2025-05-02 11:30         ` Peter Zijlstra
2025-05-02 12:04           ` Frederic Weisbecker
2025-05-02 11:58         ` Frederic Weisbecker
2025-04-24 16:11 ` [PATCH 3/4] perf: Remove too early and redundant CPU hotplug handling Frederic Weisbecker
2025-04-24 16:32   ` Peter Zijlstra
2025-05-08 10:34   ` [tip: perf/core] " tip-bot2 for Frederic Weisbecker
2025-05-08 19:55   ` tip-bot2 for Frederic Weisbecker
2025-04-24 16:11 ` [PATCH 4/4] perf: Fix confusing aux iteration Frederic Weisbecker
2025-05-08 10:34   ` [tip: perf/core] " tip-bot2 for Frederic Weisbecker
2025-05-08 19:55   ` tip-bot2 for Frederic Weisbecker

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=20250502102918.GW4198@noisy.programming.kicks-ass.net \
    --to=peterz@infradead.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alexander.shishkin@linux.intel.com \
    --cc=frederic@kernel.org \
    --cc=irogers@google.com \
    --cc=jolsa@kernel.org \
    --cc=kan.liang@linux.intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=ravi.bangoria@amd.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®