Merge tag 'perf_urgent_for_v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull perf fixes from Borislav Petkov:

 - Prevent the use of exited events as group leaders

 - Avoid use-after-free of an event's group leader by promoting detached
   sibling events to standalone entities and correct related accounting
   and state transitions

* tag 'perf_urgent_for_v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  perf/core: Fix group leader use-after-free after sibling detach
  perf: Reject exited events as group leaders
This commit is contained in:
Linus Torvalds
2026-08-16 10:31:05 -07:00

View File

@@ -2343,6 +2343,34 @@ 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_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
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;
@@ -2366,8 +2394,9 @@ static void perf_group_detach(struct perf_event *event)
*/
if (leader != event) {
list_del_init(&event->sibling_list);
event->group_leader->nr_siblings--;
event->group_leader->group_generation++;
leader->nr_siblings--;
leader->group_generation++;
perf_promote_sibling_to_leader(event, ctx, event->event_caps);
goto out;
}
@@ -2377,32 +2406,14 @@ 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);
}
event->nr_siblings = 0;
out:
for_each_sibling_event(tmp, leader)
@@ -2592,12 +2603,7 @@ __perf_remove_from_context(struct perf_event *event,
if (flags & DETACH_DEAD)
state = PERF_EVENT_STATE_DEAD;
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));
__event_disable(event, ctx, state);
if (flags & DETACH_GROUP)
perf_group_detach(event);
@@ -2666,8 +2672,9 @@ static void __event_disable(struct perf_event *event,
enum perf_event_state state)
{
event_sched_out(event, ctx);
perf_cgroup_event_disable(event, ctx);
perf_event_set_state(event, state);
if (event->state > PERF_EVENT_STATE_OFF)
perf_cgroup_event_disable(event, ctx);
perf_event_set_state(event, min(event->state, state));
}
/*
@@ -13972,7 +13979,7 @@ SYSCALL_DEFINE5(perf_event_open,
goto err_fd;
}
group_leader = fd_file(group)->private_data;
if (group_leader->state <= PERF_EVENT_STATE_REVOKED) {
if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
err = -ENODEV;
goto err_fd;
}
@@ -14103,6 +14110,12 @@ SYSCALL_DEFINE5(perf_event_open,
if (group_leader->ctx != ctx)
goto err_locked;
/* Recheck under ctx::mutex to serialize against remove-on-exec. */
if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
err = -ENODEV;
goto err_locked;
}
/*
* Only a group leader can be exclusive or pinned
*/