FAILED: patch "[PATCH] perf/core: Fix group leader use-after-free after sibling" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 42c5ca1f0a288a52878bd72a5595b08261057438
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026081730-clay-squid-bf75@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 42c5ca1f0a288a52878bd72a5595b08261057438 Mon Sep 17 00:00:00 2001
From: Aditya Chillara <[email protected]>
Date: Fri, 7 Aug 2026 18:11:52 +0530
Subject: [PATCH] perf/core: Fix group leader use-after-free after sibling
detach
perf_group_detach() handles leader and sibling detach differently. When the group leader is detached, all siblings are promoted to singleton events and their group_leader pointer is reset to themselves. When a sibling is detached, it is removed from the leader's sibling_list, but its group_leader pointer is left pointing at the old leader.
That is harmless when the sibling is being closed and freed immediately, as in the DETACH_DEAD path. It is not safe when the sibling is detached but kept alive, such as during CPU hotplug with DETACH_GROUP. In that case the sibling is removed from the context, while its file descriptor can still keep it alive.
A typical failing sequence is:
- A group contains leader L and sibling S.
- CPU hot-unplug detaches S with DETACH_GROUP, removing it from
L->sibling_list but leaving S->group_leader == L.
- L is later closed and freed.
- A PERF_IOC_FLAG_GROUP ioctl on S follows S->group_leader and dereferences the freed leader.
This was reproduced by running the perf event fuzzer, CPU hotplug, and a stress workload concurrently:
Unable to handle kernel paging request at virtual address 006b6b6b6b6b6cdb CPU: 2 PID: 12489 Comm: perf_fuzzer 6.18.7 PREEMPT pc : perf_ioctl+0x34c/0xc68 x20: ffffff89a3fa2c70 x8 : 6b6b6b6b6b6b6b6b Code: 943c4a0e 340047a0 f9404a94 f9411e88 (f940b908) Call trace: perf_ioctl+0x34c/0xc68 (P) __arm64_sys_ioctl+0xa0/0xf4 invoke_syscall+0x58/0xe4 el0_svc_common+0xa8/0xdc do_el0_svc+0x1c/0x28 el0_svc+0x40/0xc0 el0t_64_sync_handler+0x68/0xdc el0t_64_sync+0x1c4/0x1c8
The fault happened in perf_ioctl(), where perf_event_for_each() follows the stale group_leader pointer and perf_event_for_each_child() then dereferences the freed leader's context.
Fix the use-after-free by promoting the detached sibling to a singleton. Also fix __event_disable() cgroup accounting and event state change.
kernel/events/core.c +37 -30
@@ -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)); }/*
Sasha Levin
maintainerPATCH 1/2 perf: Unify perf_event_free_task() / perf_event_exit_task_context()
From: Peter Zijlstra <[email protected]>
[ Upstream commit 90661365021a6d0d7f3a2c5046ebe33e4df53b92 ]
Both perf_event_free_task() and perf_event_exit_task_context() are very similar, except perf_event_exit_task_context() is a little more generic / makes less assumptions.
Stable-dep-of: 42c5ca1f0a28 ("perf/core: Fix group leader use-after-free after sibling detach")
--- kernel/events/core.c | 96 ++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 70 deletions(-)
kernel/events/core.c +26 -70
@@ -2393,6 +2393,7 @@ __perf_remove_from_context(struct perf_event *event,perf_cgroup_event_disable (event, ctx);perf_event_set_state (event,min (event->state, state)); +if (flags &DETACH_GROUP )perf_group_detach (event);if (flags &DETACH_CHILD )
@@ -13276,13 +13277,11 @@ perf_event_exit_event(struct perf_event *event,perf_event_wakeup (event); } -static void perf_event_exit_task_context (struct task_struct *child) +static void perf_event_exit_task_context (struct task_struct *child,bool exit) {struct perf_event_context *child_ctx, *clone_ctx =NULL ;struct perf_event *child_event, *next; -WARN_ON_ONCE (child != current); - child_ctx =perf_pin_task_context (child);if (!child_ctx)return ;
@@ -13305,7 +13304,8 @@ static void perf_event_exit_task_context(struct task_struct *child)* in. */ raw_spin_lock_irq (&child_ctx->lock); -task_ctx_sched_out (child_ctx,EVENT_ALL ); +if (exit) +task_ctx_sched_out (child_ctx,EVENT_ALL );/* * Now that the context is inactive, destroy the task <-> ctx relation
@@ -13314,7 +13314,7 @@ static void perf_event_exit_task_context(struct task_struct *child)RCU_INIT_POINTER (child->perf_event_ctxp,NULL );put_ctx (child_ctx);/* cannot be last */ WRITE_ONCE (child_ctx->task,TASK_TOMBSTONE ); -put_task_struct (current);/* cannot be last */ +put_task_struct (child);/* cannot be last */ clone_ctx =unclone_ctx (child_ctx);raw_spin_unlock_irq (&child_ctx->lock);
@@ -13327,13 +13327,31 @@ static void perf_event_exit_task_context(struct task_struct *child)* won't get any samples after PERF_RECORD_EXIT. We can however still * get a few PERF_RECORD_READ events. */ -perf_event_task (child, child_ctx,0 ); +if (exit) +perf_event_task (child, child_ctx,0 );list_for_each_entry_safe (child_event, next, &child_ctx->event_list, event_entry)perf_event_exit_event (child_event, child_ctx,0 );mutex_unlock (&child_ctx->mutex); +if (!exit) { +/* +* perf_event_release_kernel() could still have a reference on +* this context. In that case we must wait for these events to +* have been freed (in particular all their references to this +* task must've been dropped). +* +* Without this copy_process() will unconditionally free this +* task (irrespective of its reference count) and +* _free_event()'s put_task_struct(event->hw.target) will be a +* use-after-free. +* +* Wait for all events to drop their context reference. +*/ +wait_var_event (&child_ctx->refcount, +refcount_read (&child_ctx->refcount) ==1 ); + }put_ctx (child_ctx); }
@@ -13361,7 +13379,7 @@ void perf_event_exit_task(struct task_struct *child) }mutex_unlock (&child->perf_event_mutex); -perf_event_exit_task_context (child); +perf_event_exit_task_context (child, true);/* * The perf_event_exit_task_context calls perf_event_task
@@ -13372,27 +13390,6 @@ void perf_event_exit_task(struct task_struct *child)perf_event_task (child,NULL ,0 ); } -static void perf_free_event (struct perf_event *event, -struct perf_event_context *ctx) -{ -struct perf_event *parent = event->parent; - -if (WARN_ON_ONCE (!parent)) -return ; - -mutex_lock (&parent->child_mutex); -list_del_init (&event->child_list); -mutex_unlock (&parent->child_mutex); - -put_event (parent); - -raw_spin_lock_irq (&ctx->lock); -perf_group_detach (event); -list_del_event (event, ctx); -raw_spin_unlock_irq (&ctx->lock); -free_event (event); -} -/* * Free a context as created by inheritance by perf_event_init_task() below, * used by fork() in case of fail.
@@ -13402,48 +13399,7 @@ static void perf_free_event(struct perf_event *event,*/ void perf_event_free_task (struct task_struct *task) { -struct perf_event_context *ctx; -struct perf_event *event, *tmp; - - ctx =rcu_access_pointer (task->perf_event_ctxp); -if (!ctx) -return ; - -mutex_lock (&ctx->mutex); -raw_spin_lock_irq (&ctx->lock); -/* -* Destroy the task <-> ctx relation and mark the context dead. -* -* This is important because even though the task hasn't been -* exposed yet the context has been (through child_list). -*/ -RCU_INIT_POINTER (task->perf_event_ctxp,NULL ); -WRITE_ONCE (ctx->task,TASK_TOMBSTONE ); -put_task_struct (task);/* cannot be last */ -raw_spin_unlock_irq (&ctx->lock); - - -list_for_each_entry_safe (event, tmp, &ctx->event_list, event_entry) -perf_free_event (event, ctx); - -mutex_unlock (&ctx->mutex); - -/* -* perf_event_release_kernel() could've stolen some of our -* child events and still have them on its free_list. In that -* case we must wait for these events to have been freed (in -* particular all their references to this task must've been -* dropped). -* -* Without this copy_process() will unconditionally free this -* task (irrespective of its reference count) and -* _free_event()'s put_task_struct(event->hw.target) will be a -* use-after-free. -* -* Wait for all events to drop their context reference. -*/ -wait_var_event (&ctx->refcount,refcount_read (&ctx->refcount) ==1 ); -put_ctx (ctx);/* must be last */ +perf_event_exit_task_context (task, false); }void perf_event_delayed_put (struct task_struct *task)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 perf/core: Fix group leader use-after-free after sibling detach
From: Aditya Chillara <[email protected]>
[ Upstream commit 42c5ca1f0a288a52878bd72a5595b08261057438 ]
perf_group_detach() handles leader and sibling detach differently. When the group leader is detached, all siblings are promoted to singleton events and their group_leader pointer is reset to themselves. When a sibling is detached, it is removed from the leader's sibling_list, but its group_leader pointer is left pointing at the old leader.
That is harmless when the sibling is being closed and freed immediately, as in the DETACH_DEAD path. It is not safe when the sibling is detached but kept alive, such as during CPU hotplug with DETACH_GROUP. In that case the sibling is removed from the context, while its file descriptor can still keep it alive.
A typical failing sequence is:
- A group contains leader L and sibling S.
- CPU hot-unplug detaches S with DETACH_GROUP, removing it from
L->sibling_list but leaving S->group_leader == L.
- L is later closed and freed.
- A PERF_IOC_FLAG_GROUP ioctl on S follows S->group_leader and dereferences the freed leader.
This was reproduced by running the perf event fuzzer, CPU hotplug, and a stress workload concurrently:
Unable to handle kernel paging request at virtual address 006b6b6b6b6b6cdb CPU: 2 PID: 12489 Comm: perf_fuzzer 6.18.7 PREEMPT pc : perf_ioctl+0x34c/0xc68 x20: ffffff89a3fa2c70 x8 : 6b6b6b6b6b6b6b6b Code: 943c4a0e 340047a0 f9404a94 f9411e88 (f940b908) Call trace: perf_ioctl+0x34c/0xc68 (P) __arm64_sys_ioctl+0xa0/0xf4 invoke_syscall+0x58/0xe4 el0_svc_common+0xa8/0xdc do_el0_svc+0x1c/0x28 el0_svc+0x40/0xc0 el0t_64_sync_handler+0x68/0xdc el0t_64_sync+0x1c4/0x1c8
The fault happened in perf_ioctl(), where perf_event_for_each() follows the stale group_leader pointer and perf_event_for_each_child() then dereferences the freed leader's context.
Fix the use-after-free by promoting the detached sibling to a singleton. Also fix __event_disable() cgroup accounting and event state change.
--- kernel/events/core.c | 67 ++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 30 deletions(-)
kernel/events/core.c +37 -30
@@ -2162,6 +2162,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;
@@ -2185,8 +2213,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; }
@@ -2196,32 +2225,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)
@@ -2387,12 +2398,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);
@@ -2462,8 +2468,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)); }/*
Signature · 1 line
2.53.0