Re: [PATCH v2] perf/core: Fix group leader use-after-free after sibling detach

Aditya Chillara <[email protected]>
Newsgroups org.kernel.vger.stable,org.kernel.vger.linux-kernel,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
On 8/7/2026 1:20 AM, Peter Zijlstra wrote:
> 
> Finally got to look at this. In principle this seems okay, but while
> staring at it, I had a few questions, see below.
> 
> On Tue, Jun 30, 2026 at 12:42:12AM +0530, Aditya Chillara wrote:
>> ---
>>  kernel/events/core.c | 62 +++++++++++++++++++++++++++++++++++-----------------
>>  1 file changed, 42 insertions(+), 20 deletions(-)
>>
>> diff --git a/kernel/events/core.c b/kernel/events/core.c
>> index 954c36e28101..744643ada948 100644
>> --- a/kernel/events/core.c
>> +++ b/kernel/events/core.c
>> @@ -2253,6 +2253,8 @@ static void put_event(struct perf_event *event);
>>  static void __event_disable(struct perf_event *event,
>>  			    struct perf_event_context *ctx,
>>  			    enum perf_event_state state);
>> +static void event_sched_out(struct perf_event *event,
>> +			    struct perf_event_context *ctx);
>>  
>>  static void perf_put_aux_event(struct perf_event *event)
>>  {
>> @@ -2343,6 +2345,44 @@ static inline struct list_head *get_event_list(struct perf_event *event)
>>  				    &event->pmu_ctx->flexible_active;
>>  }
>>  
>> +/* @sibling must already be unlinked from its old leader's sibling_list. */
>> +static void perf_promote_sibling_to_leader(struct perf_event *sibling,
>> +					   struct perf_event_context *ctx,
>> +					   int group_caps)
>> +{
>> +	/*
>> +	 * Events that have PERF_EV_CAP_SIBLING require being part of
>> +	 * a group and cannot exist on their own, schedule them out
>> +	 * and move them into the ERROR state. Also see
>> +	 * _perf_event_enable(), it will not be able to recover this
>> +	 * ERROR state.
>> +	 */
>> +	if (sibling->event_caps & PERF_EV_CAP_SIBLING) {
>> +		event_sched_out(sibling, ctx);
>> +
>> +		/*
>> +		 * The guards keep this correct even when @sibling is already
>> +		 * disabled (see __perf_remove_from_context()).
>> +		 */
>> +		if (sibling->state > PERF_EVENT_STATE_OFF)
>> +			perf_cgroup_event_disable(sibling, ctx);
>> +		if (sibling->state > PERF_EVENT_STATE_ERROR)
>> +			perf_event_set_state(sibling, PERF_EVENT_STATE_ERROR);
>> +	}
> 
> The below code used __event_disable(); and this change is not
> mentioned in the Changelog. Why was this changed?

I should've mentioned this in the Changelog, I'll update it.

In case of __perf_remove_from_context(event, ...) where the event is part of a group
but is not the leader; by the time we get to perf_group_detach():

1. perf_cgroup_event_disable(event, ctx) would have been called
2. The sibling's state may have been set to EXIT/REVOKED/DEAD if not OFF

So we cannot use __event_disable() because it would unconditionally call
1. perf_cgroup_event_disable() that would decrement nr_cgroups twice
2. perf_event_set_state(event, PERF_EVENT_STATE_ERROR) that could bring back
   the state from EXIT/REVOKED/DEAD/OFF to ERROR.

In previous code, in case of __perf_remove_from_context(event, ...) where the event
is a leader; __event_disable() was called for siblings unconditionally but I do not
see a reason why the added guards don't hold.

Should we move the guards to __event_disable() instead? That would simplify the code:

static void __event_disable(struct perf_event *event,
			    struct perf_event_context *ctx,
			    enum perf_event_state state)
{
	event_sched_out(event, ctx);
	if (event->state > PERF_EVENT_STATE_OFF)
		perf_cgroup_event_disable(event, ctx);
	perf_event_set_state(event, min(event->state, state));
}

Also, event_sched_out() in case of pending_disable calls perf_cgroup_event_disable()
and sets the state to OFF, but __event_disable() immediately calls
perf_cgroup_event_disable() again.

> 
>> +
>> +	sibling->group_leader = sibling;
>> +	sibling->group_caps = group_caps;
>> +
>> +	if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
>> +		add_event_to_groups(sibling, ctx);
>> +
>> +		if (sibling->state == PERF_EVENT_STATE_ACTIVE)
>> +			list_add_tail(&sibling->active_list, get_event_list(sibling));
>> +	}
>> +
>> +	perf_event__header_size(sibling);
>> +}
>> +
>>  static void perf_group_detach(struct perf_event *event)
>>  {
>>  	struct perf_event *leader = event->group_leader;
>> @@ -2368,6 +2408,7 @@ static void perf_group_detach(struct perf_event *event)
>>  		list_del_init(&event->sibling_list);
>>  		event->group_leader->nr_siblings--;
>>  		event->group_leader->group_generation++;
> 
> Here we can do 's/event->group_//'

I'll update this.

> 
> Also, this case 'leader != event' we remove one sibling from a group and
> decrement leader->nr_siblings...
> 
>> +		perf_promote_sibling_to_leader(event, ctx, event->event_caps);
>>  		goto out;
>>  	}
>>  
>> @@ -2377,29 +2418,10 @@ static void perf_group_detach(struct perf_event *event)
>>  	 * to whatever list we are on.
>>  	 */
>>  	list_for_each_entry_safe(sibling, tmp, &event->sibling_list, sibling_list) {
>> -
>> -		/*
>> -		 * Events that have PERF_EV_CAP_SIBLING require being part of
>> -		 * a group and cannot exist on their own, schedule them out
>> -		 * and move them into the ERROR state. Also see
>> -		 * _perf_event_enable(), it will not be able to recover this
>> -		 * ERROR state.
>> -		 */
>> -		if (sibling->event_caps & PERF_EV_CAP_SIBLING)
>> -			__event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
>> -
>> -		sibling->group_leader = sibling;
>>  		list_del_init(&sibling->sibling_list);
>>  
>>  		/* Inherit group flags from the previous leader */
>> -		sibling->group_caps = event->group_caps;
>> -
>> -		if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
>> -			add_event_to_groups(sibling, event->ctx);
>> -
>> -			if (sibling->state == PERF_EVENT_STATE_ACTIVE)
>> -				list_add_tail(&sibling->active_list, get_event_list(sibling));
>> -		}
>> +		perf_promote_sibling_to_leader(sibling, ctx, event->group_caps);
>>  
>>  		WARN_ON_ONCE(sibling->ctx != event->ctx);
>>  	}
> 
> This is the case 'leader == event' (per not being the other case), and
> this we remove all siblings, but then do not set leader->nr_siblings =
> 0, should we ?

Yes, I'll add this.

Thank you,
Aditya
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.