mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 09:20:13 -04:00
perf/core: Detach event groups during remove_on_exec
perf_event_remove_on_exec() removes events by calling
perf_event_exit_event(). For top-level events, this removes the event from
the context with DETACH_EXIT only.
This can leave inconsistent group state when a removed event is a group
leader and the group contains siblings without remove_on_exec. If the group
was active, the surviving siblings can remain active and attached to the
removed leader's sibling list, but are no longer represented by a valid
group leader on the PMU context active lists.
A later close of the removed leader uses DETACH_GROUP and can promote the
still-active siblings from this stale group state. The next schedule-in can
then add an already-linked active_list entry again, corrupting the PMU
context active list.
With DEBUG_LIST enabled, this is caught as a list_add double-add in
merge_sched_in().
Fix this by detaching group relationships when remove_on_exec removes an
event. This preserves the existing task-exit and revoke behavior, while
ensuring surviving siblings are ungrouped before the removed event leaves
the context.
Fixes: 2e498d0a74 ("perf: Add support for event removal on exec")
Signed-off-by: Taeyang Lee <0wn@theori.io>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/ai65GgZcC0LAlWLG@Taeyangs-MacBook-Pro.local
This commit is contained in:
committed by
Peter Zijlstra
parent
dc59e4fea9
commit
037a3c43ed
@@ -4729,7 +4729,7 @@ static void perf_remove_from_owner(struct perf_event *event);
|
|||||||
static void perf_event_exit_event(struct perf_event *event,
|
static void perf_event_exit_event(struct perf_event *event,
|
||||||
struct perf_event_context *ctx,
|
struct perf_event_context *ctx,
|
||||||
struct task_struct *task,
|
struct task_struct *task,
|
||||||
bool revoke);
|
unsigned long detach_flags);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Removes all events from the current task that have been marked
|
* Removes all events from the current task that have been marked
|
||||||
@@ -4756,7 +4756,7 @@ static void perf_event_remove_on_exec(struct perf_event_context *ctx)
|
|||||||
|
|
||||||
modified = true;
|
modified = true;
|
||||||
|
|
||||||
perf_event_exit_event(event, ctx, ctx->task, false);
|
perf_event_exit_event(event, ctx, ctx->task, DETACH_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
raw_spin_lock_irqsave(&ctx->lock, flags);
|
raw_spin_lock_irqsave(&ctx->lock, flags);
|
||||||
@@ -12937,7 +12937,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event,
|
|||||||
/*
|
/*
|
||||||
* De-schedule the event and mark it REVOKED.
|
* De-schedule the event and mark it REVOKED.
|
||||||
*/
|
*/
|
||||||
perf_event_exit_event(event, ctx, ctx->task, true);
|
perf_event_exit_event(event, ctx, ctx->task, DETACH_REVOKE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* All _free_event() bits that rely on event->pmu:
|
* All _free_event() bits that rely on event->pmu:
|
||||||
@@ -14525,12 +14525,13 @@ static void
|
|||||||
perf_event_exit_event(struct perf_event *event,
|
perf_event_exit_event(struct perf_event *event,
|
||||||
struct perf_event_context *ctx,
|
struct perf_event_context *ctx,
|
||||||
struct task_struct *task,
|
struct task_struct *task,
|
||||||
bool revoke)
|
unsigned long detach_flags)
|
||||||
{
|
{
|
||||||
struct perf_event *parent_event = event->parent;
|
struct perf_event *parent_event = event->parent;
|
||||||
unsigned long detach_flags = DETACH_EXIT;
|
|
||||||
unsigned int attach_state;
|
unsigned int attach_state;
|
||||||
|
|
||||||
|
detach_flags |= DETACH_EXIT;
|
||||||
|
|
||||||
if (parent_event) {
|
if (parent_event) {
|
||||||
/*
|
/*
|
||||||
* Do not destroy the 'original' grouping; because of the
|
* Do not destroy the 'original' grouping; because of the
|
||||||
@@ -14553,8 +14554,8 @@ perf_event_exit_event(struct perf_event *event,
|
|||||||
sync_child_event(event, task);
|
sync_child_event(event, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revoke)
|
if (detach_flags & DETACH_REVOKE)
|
||||||
detach_flags |= DETACH_GROUP | DETACH_REVOKE;
|
detach_flags |= DETACH_GROUP;
|
||||||
|
|
||||||
perf_remove_from_context(event, detach_flags);
|
perf_remove_from_context(event, detach_flags);
|
||||||
/*
|
/*
|
||||||
@@ -14642,7 +14643,7 @@ static void perf_event_exit_task_context(struct task_struct *task, bool exit)
|
|||||||
perf_event_task(task, ctx, 0);
|
perf_event_task(task, ctx, 0);
|
||||||
|
|
||||||
list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry)
|
list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry)
|
||||||
perf_event_exit_event(child_event, ctx, exit ? task : NULL, false);
|
perf_event_exit_event(child_event, ctx, exit ? task : NULL, 0);
|
||||||
|
|
||||||
mutex_unlock(&ctx->mutex);
|
mutex_unlock(&ctx->mutex);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user