[PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events
From: Tengda Wu
Date: Thu Nov 05 2026 - 04:31:38 EST
perf_sigtrap() contains:
WARN_ON_ONCE(event->ctx->task != current)
The warning assumes that an event's ctx->task always matches the task
that ends up running the pending task_work. That assumption can be
broken by the context fast-swap path.
The failure sequence is:
1. A -> B switch, perf_event_context_sched_out() takes the fast path
and does WRITE_ONCE(ctx->task, next) / WRITE_ONCE(next_ctx->task, task).
An NMI then hits while the context is swapped, and
__perf_event_overflow() queues pending task_work on the *current*
task (A), even though the event now belongs to B's swapped-in
context view.
2. B -> A switch. Because A now has pending task_work,
nr_no_switch_fast is non-zero, so the slow path is taken and
ctx->task is *not* swapped back.
3. A returns to user mode, task_work runs perf_pending_task() ->
perf_sigtrap(), and event->ctx->task no longer equals current,
tripping the WARN_ON_ONCE().
The context fast-swap path was already excluded for events that need
stable task association (inherit + sample read). sigtrap events have
the same requirement: the SIGTRAP is targeted at ctx->task and must not
be delivered after ctx->task has been swapped to another task.
Fix this by also excluding events with attr->sigtrap from the fast-swap
path, i.e. bump nr_no_switch_fast for sigtrap events in
list_add_event()/list_del_event(), and update the comment describing
the invariant accordingly.
Fixes: c5d93d23a260 ("perf: Enqueue SIGTRAP always via task_work.")
Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
include/linux/perf_event.h | 6 ++----
kernel/events/core.c | 9 +++++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index eb3f7bab498c..5cc0fb144e04 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -1068,10 +1068,8 @@ struct perf_event_context {
* should be avoided.
*
* Sum (event->pending_work + events with
- * (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ)))
- *
- * The SIGTRAP is targeted at ctx->task, as such it won't do changing
- * that until the signal is delivered.
+ * (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ)) +
+ * events with attr->sigtrap)
*/
local_t nr_no_switch_fast;
};
diff --git a/kernel/events/core.c b/kernel/events/core.c
index ee5c59158b89..d693a14ca50d 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -1999,7 +1999,7 @@ list_add_event(struct perf_event *event, struct perf_event_context *ctx)
ctx->nr_user++;
if (event->attr.inherit_stat)
ctx->nr_stat++;
- if (has_inherit_and_sample_read(&event->attr))
+ if (has_inherit_and_sample_read(&event->attr) || event->attr.sigtrap)
local_inc(&ctx->nr_no_switch_fast);
if (event->state > PERF_EVENT_STATE_OFF)
@@ -2225,7 +2225,7 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx)
ctx->nr_user--;
if (event->attr.inherit_stat)
ctx->nr_stat--;
- if (has_inherit_and_sample_read(&event->attr))
+ if (has_inherit_and_sample_read(&event->attr) || event->attr.sigtrap)
local_dec(&ctx->nr_no_switch_fast);
list_del_rcu(&event->event_entry);
@@ -3831,6 +3831,11 @@ perf_event_context_sched_out(struct task_struct *task, struct task_struct *next)
* SAMPLE_READ events they should be switched
* out using the slow path so that they are
* treated as if they were distinct contexts.
+ *
+ * A context holding a sigtrap event must also
+ * take the slow path, even when nothing is
+ * pending, since the sigtrap delivery relies on
+ * the ctx->task relation as well.
*/
raw_spin_unlock(&next_ctx->lock);
rcu_read_unlock();
--
2.34.1