mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
@ 2026-11-05  9:30 Tengda Wu
  2026-11-05  9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05  9:30 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
	linux-perf-users, linux-kernel, Tengda Wu

Two fixes for sigtrap task_work handling since the switch to always
enqueue SIGTRAP via task_work.

The warning splats and PoCs for both patches are too long to inline in
the commit messages. They are sent as separate replies to this cover
letter, one per patch.

Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
was set only after task_work_add(), so an NMI could re-enter and enqueue
the same pending_task twice, creating a cycle in the task work list and
unbalancing the event refcount. Claim pending_work with cmpxchg() before
enqueuing, and clear it if the enqueue fails.

Patch 2 fixes a WARN_ON_ONCE() in perf_sigtrap() triggered by the context
fast-swap path swapping ctx->task while a sigtrap event has pending task
work. SIGTRAP is targeted at ctx->task, so exclude sigtrap events from
fast-swap alongside inherit+sample-read events.

Changelog
=========
v2 -> v3:
  - v2: https://lore.kernel.org/all/20261102021355.650671-1-wutengda@huaweicloud.com/
  - Added Patch 2 to fix WARN_ON_ONCE() in perf_sigtrap()
v1 -> v2:
  - v1: https://lore.kernel.org/all/20261031114520.642476-1-wutengda@huaweicloud.com/
  - Drop the redundant event->pending_work assignment. (Sashiko)

Tengda Wu (2):
  perf/core: Prevent duplicate sigtrap task work enqueue
  perf/core: Disable context fast-swap for sigtrap events

 include/linux/perf_event.h |  6 ++----
 kernel/events/core.c       | 30 ++++++++++++++++++------------
 2 files changed, 20 insertions(+), 16 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue
  2026-11-05  9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
@ 2026-11-05  9:30 ` Tengda Wu
  2026-11-05  9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05  9:30 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
	linux-perf-users, linux-kernel, Tengda Wu

The sigtrap handling in __perf_event_overflow() checks
event->pending_work before calling task_work_add(), but only sets
pending_work after the task work has been queued.

This leaves a window where an NMI can interrupt task_work_add()
and re-enter __perf_event_overflow() for the same event. Since
pending_work is still zero, the nested invocation can enqueue
the same pending_task again.

The re-entrant call path is:

  __perf_event_overflow()
    task_work_add()
      kasan_record_aux_stack()
        <NMI>
          __perf_event_overflow()
            task_work_add()

The nested invocation can enqueue pending_task before the
outer invocation does. When the outer invocation resumes, it
inserts the same task_work entry again, potentially creating
a cycle in the task work list.

This causes perf_pending_task() to be executed repeatedly
and leads to unbalanced event reference counting.

Use cmpxchg() to atomically claim pending_work before calling
task_work_add(). This ensures that only one invocation can
enqueue the pending_task, even when interrupted by an NMI.

If task_work_add() fails, clear the pending_work claim so that
a subsequent overflow can retry.

Also use READ_ONCE() when checking pending_work in the
exclude_kernel warning path to avoid an unsynchronized read.

Fixes: c5d93d23a260 ("perf: Enqueue SIGTRAP always via task_work.")
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
---
 kernel/events/core.c | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 457d75ea7142..ee5c59158b89 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -10925,16 +10925,17 @@ static int __perf_event_overflow(struct perf_event *event,
 
 		notify_mode = in_nmi() ? TWA_NMI_CURRENT : TWA_RESUME;
 
-		if (!event->pending_work &&
-		    !task_work_add(current, &event->pending_task, notify_mode)) {
-			event->pending_work = pending_id;
-			local_inc(&event->ctx->nr_no_switch_fast);
-			WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));
-
-			event->pending_addr = 0;
-			if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR))
-				event->pending_addr = data->addr;
-
+		if (cmpxchg(&event->pending_work, 0, pending_id) == 0) {
+			if (!task_work_add(current, &event->pending_task, notify_mode)) {
+				local_inc(&event->ctx->nr_no_switch_fast);
+				WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));
+
+				event->pending_addr = 0;
+				if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR))
+					event->pending_addr = data->addr;
+			} else {
+				cmpxchg(&event->pending_work, pending_id, 0);
+			}
 		} else if (event->attr.exclude_kernel && valid_sample) {
 			/*
 			 * Should not be able to return to user space without
@@ -10948,7 +10949,7 @@ static int __perf_event_overflow(struct perf_event *event,
 			 *     To approximate progress (with false negatives),
 			 *     check 32-bit hash of the current IP.
 			 */
-			WARN_ON_ONCE(event->pending_work != pending_id);
+			WARN_ON_ONCE(READ_ONCE(event->pending_work) != pending_id);
 		}
 	}
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events
  2026-11-05  9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
  2026-11-05  9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
@ 2026-11-05  9:30 ` Tengda Wu
  2026-11-05  9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
  2026-11-05  9:47 ` Tengda Wu
  3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05  9:30 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
	linux-perf-users, linux-kernel, Tengda Wu

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@huaweicloud.com>
---
 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


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
  2026-11-05  9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
  2026-11-05  9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
  2026-11-05  9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
@ 2026-11-05  9:43 ` Tengda Wu
  2026-11-05  9:47 ` Tengda Wu
  3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05  9:43 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
	linux-perf-users, linux-kernel



On 2026/11/5 17:30, Tengda Wu wrote:
> Two fixes for sigtrap task_work handling since the switch to always
> enqueue SIGTRAP via task_work.
> 
> The warning splats and PoCs for both patches are too long to inline in
> the commit messages. They are sent as separate replies to this cover
> letter, one per patch.
> 
> Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
> was set only after task_work_add(), so an NMI could re-enter and enqueue
> the same pending_task twice, creating a cycle in the task work list and
> unbalancing the event refcount. Claim pending_work with cmpxchg() before
> enqueuing, and clear it if the enqueue fails.
> 

This is the warning splat and PoC for patch 1
("perf/core: Prevent duplicate sigtrap task work enqueue").

Warning:

[   67.686591] ------------[ cut here ]------------
[   67.686598] WARNING: kernel/events/core.c:10932 at __perf_event_overflow+0xa48/0xbf0, CPU#1: gpt/1709
[   67.688569] Modules linked in:
[   67.689017] CPU: 1 UID: 0 PID: 1709 Comm: gpt Not tainted 7.3.0-next-20261027 #2 PREEMPT(lazy) 
[   67.690194] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[   67.691718] RIP: 0010:__perf_event_overflow+0xa48/0xbf0
[   67.692431] Code: ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 90 01 00 00 48 8b 04 24 48 8b 80 b8 00 00 00 49 89 86 50 0
[   67.694915] RSP: 0000:fffffe0000058888 EFLAGS: 00010046
[   67.695633] RAX: 0000000000000000 RBX: 1fffffc00000b119 RCX: 0000000000000001
[   67.696594] RDX: 1ffff11023118a5e RSI: 0000000000000008 RDI: ffff8881188c52f0
[   67.697552] RBP: ffff8881188c51cc R08: 0000000000000001 R09: ffffed1023118a5e
[   67.698519] R10: ffff8881188c52f7 R11: ffff8881188c5428 R12: ffff8881f70b3538
[   67.699480] R13: ffff8881188c52f0 R14: ffff8881188c5080 R15: ffff8881188c53c4
[   67.700445] FS:  00007fed98fc6600(0000) GS:ffff88826ff74000(0000) knlGS:0000000000000000
[   67.701532] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   67.702317] CR2: 00007fed98d06000 CR3: 000000010507c004 CR4: 0000000000170ef0
[   67.703281] Call Trace:
[   67.703636]  <NMI>
[   67.703936]  ? __pfx___perf_event_overflow+0x10/0x10
[   67.704629]  ? perf_swevent_set_period+0x131/0x1c0
[   67.705288]  ? __pfx_perf_swevent_set_period+0x10/0x10
[   67.705992]  ? __pfx_perf_event_output_forward+0x10/0x10
[   67.706726]  perf_swevent_event+0x1d0/0x390
[   67.707304]  perf_tp_event+0x3b3/0x8c0
[   67.707831]  ? __perf_event_account_interrupt+0x24/0x570
[   67.708562]  ? __pfx_perf_tp_event+0x10/0x10
[   67.709158]  ? __pfx_x86_pmu_update_perf_regs+0x10/0x10
[   67.709879]  ? handle_pmi_common+0x42f/0x8a0
[   67.710475]  ? __pfx_handle_pmi_common+0x10/0x10
[   67.711118]  perf_trace_msr_trace_class+0x28e/0x450
[   67.711797]  ? __pfx_perf_trace_msr_trace_class+0x10/0x10
[   67.712532]  ? intel_bts_interrupt+0x71/0x4c0
[   67.713146]  do_trace_write_msr+0xaa/0xe0
[   67.713707]  __intel_pmu_enable_all.constprop.0+0x1db/0x350
[   67.714467]  intel_pmu_handle_irq+0x721/0xb70
[   67.715079]  ? __pfx_mce_raise_notify+0x10/0x10
[   67.715711]  ? irq_work_queue+0x7a/0xc0
[   67.716248]  perf_event_nmi_handler+0x13c/0x1a0
[   67.716885]  nmi_handle.part.0+0xb9/0x350
[   67.717443]  ? find_bug+0x3d/0x230
[   67.717931]  default_do_nmi+0x6b/0x180
[   67.718458]  exc_nmi+0xf3/0x120
[   67.718910]  end_repeat_nmi+0xf/0x53
[   67.719413] RIP: 0010:find_bug+0x3d/0x230
[   67.719975] Code: 48 89 fb 48 3d b0 65 0d 86 0f 83 8c 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 c1 83 e0 07 48 c1 e9 4
[   67.722448] RSP: 0000:ffff888118957ae0 EFLAGS: 00000006
[   67.723166] RAX: 0000000000000003 RBX: ffffffff81a666e5 RCX: 1ffffffff0c14c80
[   67.724129] RDX: 0000000000000000 RSI: ffffffff81a666e5 RDI: ffffffff81a666e5
[   67.725098] RBP: ffffffff8713f810 R08: 0000000000000000 R09: 0000000000000000
[   67.726066] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff81a666e5
[   67.727033] R13: ffff888118957c88 R14: 0000000000000000 R15: 0000000000000000
[   67.728000]  ? put_pmu_ctx+0x255/0x300
[   67.728528]  ? put_pmu_ctx+0x255/0x300
[   67.729056]  ? put_pmu_ctx+0x255/0x300
[   67.729583]  ? put_pmu_ctx+0x255/0x300
[   67.730111]  ? find_bug+0x3d/0x230
[   67.730597]  ? find_bug+0x3d/0x230
[   67.731077]  </NMI>
[   67.731383]  <TASK>
[   67.731694]  ? put_pmu_ctx+0x255/0x300
[   67.732217]  __report_bug+0x35a/0x3e0
[   67.732736]  ? put_pmu_ctx+0x255/0x300
[   67.733261]  ? __kasan_slab_free+0x43/0x70
[   67.733834]  ? __pfx___report_bug+0x10/0x10
[   67.734411]  ? task_work_run+0x13d/0x200
[   67.734962]  ? irqentry_exit+0x3a8/0x700
[   67.735507]  ? asm_exc_page_fault+0x26/0x30
[   67.736090]  ? __send_signal_locked+0x3c0/0xb40
[   67.736726]  ? __send_signal_locked+0x52f/0xb40
[   67.737358]  ? put_pmu_ctx+0x255/0x300
[   67.737886]  report_bug+0x95/0x1a0
[   67.738368]  ? put_pmu_ctx+0x255/0x300
[   67.738897]  handle_bug+0x267/0x2d0
[   67.739387]  exc_invalid_op+0x18/0x50
[   67.739904]  asm_exc_invalid_op+0x1a/0x20
[   67.740459] RIP: 0010:put_pmu_ctx+0x255/0x300
[   67.741067] Code: 75 4a 48 83 c4 68 5b 5d 41 5c 41 5d 41 5e 41 5f e9 50 8b d4 02 48 c7 c6 80 57 a6 81 e8 e4 af b1 ff eb c
[   67.743549] RSP: 0000:ffff888118957d30 EFLAGS: 00010083
[   67.744268] RAX: ffff8881188c50a0 RBX: ffff888104258f00 RCX: 1ffff110209df084
[   67.745235] RDX: 1ffff1102084b1e4 RSI: 0000000000000004 RDI: ffff888107949218
[   67.746201] RBP: 1ffff1102312afa7 R08: 0000000000000001 R09: ffffed102084b1ea
[   67.747167] R10: ffff888104258f53 R11: ffffffff810012a6 R12: ffff888104258f20
[   67.748132] R13: ffff888104258f08 R14: ffff888104ef8400 R15: ffff888104ef8420
[   67.749098]  ? asm_exc_page_fault+0x26/0x30
[   67.749680]  ? __pfx_put_pmu_ctx+0x10/0x10
[   67.750246]  ? mutex_unlock+0x82/0xd0
[   67.750763]  ? __pfx_mutex_unlock+0x10/0x10
[   67.751342]  __free_event+0x1cf/0x680
[   67.751855]  ? _raw_spin_lock_irq+0x8b/0xe0
[   67.752436]  perf_pending_task+0x101/0x330
[   67.753009]  ? count_memcg_events+0x273/0x410
[   67.753620]  task_work_run+0x13d/0x200
[   67.754146]  ? __pfx_task_work_run+0x10/0x10
[   67.754744]  ? __do_sys_rt_sigreturn+0x161/0x1b0
[   67.755385]  irqentry_exit+0x3a8/0x700
[   67.755912]  asm_exc_page_fault+0x26/0x30
[   67.756467] RIP: 0033:0x7fed98f3cd3a
[   67.756979] Code: 0f 82 0a 01 00 00 48 83 fa 40 77 7d c5 fe 7f 44 17 e0 c5 fe 7f 07 c5 f8 77 c3 66 0f 1f 44 00 00 48 89 0
[   67.759451] RSP: 002b:00007ffdb7dc7628 EFLAGS: 00010206
[   67.760168] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000119010
[   67.761133] RDX: 00007fed985c7010 RSI: 0000000000000000 RDI: 00007fed98cae000
[   67.762101] RBP: 00007ffdb7dc7690 R08: 00007fed985c7010 R09: 0000000000000000
[   67.763069] R10: 0000000000000022 R11: 0000000000000246 R12: 00007ffdb7dc7918
[   67.764033] R13: 0000000000401976 R14: 00007fed98fffaa0 R15: 0000000000403e08
[   67.765001]  </TASK>
[   67.765320] Kernel panic - not syncing: kernel: panic_on_warn set ...
[   67.766196] CPU: 1 UID: 0 PID: 1709 Comm: gpt Not tainted 7.3.0-next-20261027 #2 PREEMPT(lazy) 
[   67.767370] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[   67.768902] Call Trace:
[   67.769254]  <NMI>
[   67.769551]  vpanic+0x4be/0x540
[   67.770012]  ? __perf_event_overflow+0xa48/0xbf0
[   67.770653]  panic+0xc2/0xd0
[   67.771068]  ? __pfx_panic+0x10/0x10
[   67.771575]  check_panic_on_warn+0x7e/0x80
[   67.772145]  __warn+0xef/0x2b0
[   67.772586]  __report_bug+0x312/0x3e0
[   67.773102]  ? __perf_event_overflow+0xa48/0xbf0
[   67.773743]  ? __pfx___report_bug+0x10/0x10
[   67.774323]  ? perf_tp_event+0x3b3/0x8c0
[   67.774872]  ? arch_irq_work_raise+0x3f/0x60
[   67.775467]  ? __irq_work_queue_local+0x16b/0x3d0
[   67.776117]  ? __perf_event_overflow+0xa48/0xbf0
[   67.776759]  report_bug+0x95/0x1a0
[   67.777241]  ? __perf_event_overflow+0xa48/0xbf0
[   67.777883]  handle_bug+0x267/0x2d0
[   67.778375]  exc_invalid_op+0x18/0x50
[   67.778895]  asm_exc_invalid_op+0x1a/0x20
[   67.779451] RIP: 0010:__perf_event_overflow+0xa48/0xbf0
[   67.780169] Code: ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 90 01 00 00 48 8b 04 24 48 8b 80 b8 00 00 00 49 89 86 50 0
[   67.782645] RSP: 0000:fffffe0000058888 EFLAGS: 00010046
[   67.783359] RAX: 0000000000000000 RBX: 1fffffc00000b119 RCX: 0000000000000001
[   67.784326] RDX: 1ffff11023118a5e RSI: 0000000000000008 RDI: ffff8881188c52f0
[   67.785292] RBP: ffff8881188c51cc R08: 0000000000000001 R09: ffffed1023118a5e
[   67.786258] R10: ffff8881188c52f7 R11: ffff8881188c5428 R12: ffff8881f70b3538
[   67.787223] R13: ffff8881188c52f0 R14: ffff8881188c5080 R15: ffff8881188c53c4
[   67.788191]  ? __perf_event_overflow+0x975/0xbf0
[   67.788833]  ? __pfx___perf_event_overflow+0x10/0x10
[   67.789514]  ? perf_swevent_set_period+0x131/0x1c0
[   67.790179]  ? __pfx_perf_swevent_set_period+0x10/0x10
[   67.790888]  ? __pfx_perf_event_output_forward+0x10/0x10
[   67.791618]  perf_swevent_event+0x1d0/0x390
[   67.792197]  perf_tp_event+0x3b3/0x8c0
[   67.792726]  ? __perf_event_account_interrupt+0x24/0x570
[   67.793453]  ? __pfx_perf_tp_event+0x10/0x10
[   67.794051]  ? __pfx_x86_pmu_update_perf_regs+0x10/0x10
[   67.794770]  ? handle_pmi_common+0x42f/0x8a0
[   67.795366]  ? __pfx_handle_pmi_common+0x10/0x10
[   67.796009]  perf_trace_msr_trace_class+0x28e/0x450
[   67.796686]  ? __pfx_perf_trace_msr_trace_class+0x10/0x10
[   67.797424]  ? intel_bts_interrupt+0x71/0x4c0
[   67.798035]  do_trace_write_msr+0xaa/0xe0
[   67.798596]  __intel_pmu_enable_all.constprop.0+0x1db/0x350
[   67.799357]  intel_pmu_handle_irq+0x721/0xb70
[   67.799965]  ? __pfx_mce_raise_notify+0x10/0x10
[   67.800600]  ? irq_work_queue+0x7a/0xc0
[   67.801136]  perf_event_nmi_handler+0x13c/0x1a0
[   67.801770]  nmi_handle.part.0+0xb9/0x350
[   67.802329]  ? find_bug+0x3d/0x230
[   67.802816]  default_do_nmi+0x6b/0x180
[   67.803343]  exc_nmi+0xf3/0x120
[   67.803795]  end_repeat_nmi+0xf/0x53
[   67.804298] RIP: 0010:find_bug+0x3d/0x230
[   67.804858] Code: 48 89 fb 48 3d b0 65 0d 86 0f 83 8c 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 c1 83 e0 07 48 c1 e9 4
[   67.807329] RSP: 0000:ffff888118957ae0 EFLAGS: 00000006
[   67.808047] RAX: 0000000000000003 RBX: ffffffff81a666e5 RCX: 1ffffffff0c14c80
[   67.809012] RDX: 0000000000000000 RSI: ffffffff81a666e5 RDI: ffffffff81a666e5
[   67.809978] RBP: ffffffff8713f810 R08: 0000000000000000 R09: 0000000000000000
[   67.810943] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff81a666e5
[   67.811909] R13: ffff888118957c88 R14: 0000000000000000 R15: 0000000000000000
[   67.812877]  ? put_pmu_ctx+0x255/0x300
[   67.813401]  ? put_pmu_ctx+0x255/0x300
[   67.813930]  ? put_pmu_ctx+0x255/0x300
[   67.814453]  ? put_pmu_ctx+0x255/0x300
[   67.814984]  ? find_bug+0x3d/0x230
[   67.815465]  ? find_bug+0x3d/0x230
[   67.815948]  </NMI>
[   67.816255]  <TASK>
[   67.816567]  ? put_pmu_ctx+0x255/0x300
[   67.817092]  __report_bug+0x35a/0x3e0
[   67.817610]  ? put_pmu_ctx+0x255/0x300
[   67.818134]  ? __kasan_slab_free+0x43/0x70
[   67.818705]  ? __pfx___report_bug+0x10/0x10
[   67.819283]  ? task_work_run+0x13d/0x200
[   67.819833]  ? irqentry_exit+0x3a8/0x700
[   67.820378]  ? asm_exc_page_fault+0x26/0x30
[   67.820959]  ? __send_signal_locked+0x3c0/0xb40
[   67.821592]  ? __send_signal_locked+0x52f/0xb40
[   67.822221]  ? put_pmu_ctx+0x255/0x300
[   67.822749]  report_bug+0x95/0x1a0
[   67.823230]  ? put_pmu_ctx+0x255/0x300
[   67.823759]  handle_bug+0x267/0x2d0
[   67.824250]  exc_invalid_op+0x18/0x50
[   67.824766]  asm_exc_invalid_op+0x1a/0x20
[   67.825322] RIP: 0010:put_pmu_ctx+0x255/0x300
[   67.825932] Code: 75 4a 48 83 c4 68 5b 5d 41 5c 41 5d 41 5e 41 5f e9 50 8b d4 02 48 c7 c6 80 57 a6 81 e8 e4 af b1 ff eb c
[   67.828401] RSP: 0000:ffff888118957d30 EFLAGS: 00010083
[   67.829117] RAX: ffff8881188c50a0 RBX: ffff888104258f00 RCX: 1ffff110209df084
[   67.830083] RDX: 1ffff1102084b1e4 RSI: 0000000000000004 RDI: ffff888107949218
[   67.831046] RBP: 1ffff1102312afa7 R08: 0000000000000001 R09: ffffed102084b1ea
[   67.832011] R10: ffff888104258f53 R11: ffffffff810012a6 R12: ffff888104258f20
[   67.832978] R13: ffff888104258f08 R14: ffff888104ef8400 R15: ffff888104ef8420
[   67.833948]  ? asm_exc_page_fault+0x26/0x30
[   67.834526]  ? __pfx_put_pmu_ctx+0x10/0x10
[   67.835097]  ? mutex_unlock+0x82/0xd0
[   67.835613]  ? __pfx_mutex_unlock+0x10/0x10
[   67.836192]  __free_event+0x1cf/0x680
[   67.836707]  ? _raw_spin_lock_irq+0x8b/0xe0
[   67.837289]  perf_pending_task+0x101/0x330
[   67.837862]  ? count_memcg_events+0x273/0x410
[   67.838470]  task_work_run+0x13d/0x200
[   67.839000]  ? __pfx_task_work_run+0x10/0x10
[   67.839599]  ? __do_sys_rt_sigreturn+0x161/0x1b0
[   67.840238]  irqentry_exit+0x3a8/0x700
[   67.840767]  asm_exc_page_fault+0x26/0x30
[   67.841323] RIP: 0033:0x7fed98f3cd3a
[   67.841827] Code: 0f 82 0a 01 00 00 48 83 fa 40 77 7d c5 fe 7f 44 17 e0 c5 fe 7f 07 c5 f8 77 c3 66 0f 1f 44 00 00 48 89 0
[   67.844298] RSP: 002b:00007ffdb7dc7628 EFLAGS: 00010206
[   67.845015] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000119010
[   67.845980] RDX: 00007fed985c7010 RSI: 0000000000000000 RDI: 00007fed98cae000
[   67.846945] RBP: 00007ffdb7dc7690 R08: 00007fed985c7010 R09: 0000000000000000
[   67.847909] R10: 0000000000000022 R11: 0000000000000246 R12: 00007ffdb7dc7918
[   67.848876] R13: 0000000000401976 R14: 00007fed98fffaa0 R15: 0000000000403e08
[   67.849845]  </TASK>
[   67.850285] Dumping ftrace buffer:
[   67.850779]    (ftrace buffer empty)
[   67.851279] Kernel Offset: disabled
[   67.851772] ---[ end Kernel panic - not syncing: kernel: panic_on_warn set ... ]---

PoC:

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include "perf_event.h"
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static volatile sig_atomic_t sigtrap_count;

static void sigtrap_handler(int sig, siginfo_t *info, void *ctx)
{
	(void)sig;
	(void)info;
	(void)ctx;
	sigtrap_count++;
}

static int perf_open(struct perf_event_attr *attr, pid_t pid, int cpu)
{
	return syscall(__NR_perf_event_open, attr, pid, cpu, -1,
		       PERF_FLAG_FD_CLOEXEC);
}

static int read_msr_tracepoint_id(void)
{
	static const char * const paths[] = {
		"/sys/kernel/tracing/events/msr/write_msr/id",
		"/sys/kernel/debug/tracing/events/msr/write_msr/id",
	};
	char buf[64];
	unsigned int i;

	for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
		int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
		ssize_t n;

		if (fd < 0)
			continue;
		n = read(fd, buf, sizeof(buf) - 1);
		close(fd);
		if (n <= 0)
			continue;
		buf[n] = '\0';
		return (int)strtol(buf, NULL, 10);
	}
	return -1;
}

static int open_msr_sigtrap_event(int tracepoint_id, int cpu)
{
	struct perf_event_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.size = sizeof(attr);
	attr.type = PERF_TYPE_TRACEPOINT;
	attr.config = tracepoint_id;
	attr.sample_period = 4;
	attr.sample_type = PERF_SAMPLE_IP;
	attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
			   PERF_FORMAT_TOTAL_TIME_RUNNING;
	attr.pinned = 1;
	attr.exclusive = 1;
	attr.exclude_idle = 1;
	attr.exclude_user = 0;
	attr.exclude_kernel = 0;
	attr.remove_on_exec = 1;
	attr.sigtrap = 1;
	attr.sig_data = 7;

	/* pid=0 makes this a current-task event constrained to @cpu. */
	return perf_open(&attr, 0, cpu);
}

static int open_cache_miss_event(int cpu)
{
	struct perf_event_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.size = sizeof(attr);
	attr.type = PERF_TYPE_HARDWARE;
	attr.config = PERF_COUNT_HW_CACHE_MISSES; /* original config == 3 */
	attr.sample_period = 100;
	attr.sample_type = PERF_SAMPLE_IP;
	attr.pinned = 1;
	attr.exclusive = 1;
	attr.exclude_idle = 1;
	attr.exclude_user = 0;
	attr.exclude_kernel = 0;
	/* Do not set sigtrap here: the tracepoint event must own pending_task. */

	return perf_open(&attr, 0, cpu);
}

static int set_cpu(int cpu)
{
	cpu_set_t set;

	CPU_ZERO(&set);
	CPU_SET(cpu, &set);
	return sched_setaffinity(0, sizeof(set), &set);
}

static void workload(unsigned int ms)
{
	struct timespec start, now;
	uint64_t elapsed;
	const size_t bytes = 8 * 1024 * 1024;
	volatile unsigned char *buf = malloc(bytes);
	volatile uint64_t x = 1;
	uint32_t index = 0x12345678;

	if (buf)
		memset((void *)buf, 0, bytes);
	clock_gettime(CLOCK_MONOTONIC, &start);
	do {
		for (unsigned int i = 0; i < 100000; i++) {
			x = x * 1664525 + 1013904223;
			/* Pseudo-random cache-line accesses over an 8 MiB set. */
			index = index * 1664525 + 1013904223;
			if (buf)
				buf[(index & (bytes - 1)) & ~63U]++;
		}
		clock_gettime(CLOCK_MONOTONIC, &now);
		elapsed = (uint64_t)(now.tv_sec - start.tv_sec) * 1000;
		if (now.tv_nsec >= start.tv_nsec)
			elapsed += (uint64_t)(now.tv_nsec - start.tv_nsec) / 1000000;
		else
			elapsed -= (uint64_t)(start.tv_nsec - now.tv_nsec) / 1000000;
	} while (elapsed < ms);
	free((void *)buf);
	(void)x;
}

/* One short-lived task, matching syzkaller's fork/execute/exit pattern. */
static void one_child(int cpu, int tracepoint_id)
{
	struct sigaction sa;
	int tp_fd, hw_fd;

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sigtrap_handler;
	sa.sa_flags = SA_SIGINFO | SA_RESTART;
	sigemptyset(&sa.sa_mask);
	if (sigaction(SIGTRAP, &sa, NULL))
		_exit(10);

	/* As in the original POC, constrain the task after opening events. */
        if (set_cpu(cpu)) {
                dprintf(2, "[%d] sched_setaffinity failed: %s\n",
                        getpid(), strerror(errno));
                _exit(13);
        }
	/*
	 * Open the hardware event first (lower fd), then the tracepoint event
	 * (higher fd). Linux 6.6 close_files() walks fds upward during exit:
	 *
	 *   1. hardware fd closes first and PMU teardown writes MSRs;
	 *   2. msr:write_msr is still subscribed and can queue task_work;
	 *   3. tracepoint fd closes, dropping its file reference;
	 *   4. exit_task_work() runs perf_pending_task(); put_event() can now
	 *      be the final reference and reach perf_trace_event_unreg().
	 *
	 * This ordering is intentional and is the main difference from the
	 * earlier reproducer.
	 */
	hw_fd = open_cache_miss_event(cpu);
	if (hw_fd < 0) {
		dprintf(2, "[%d] open cache-misses failed: %s\n",
			getpid(), strerror(errno));
		_exit(11);
	}

	tp_fd = open_msr_sigtrap_event(tracepoint_id, cpu);
	if (tp_fd < 0) {
		dprintf(2, "[%d] open msr tracepoint failed: %s\n",
			getpid(), strerror(errno));
		_exit(12);
	}

	workload(5);

	/* Do not close either fd here. Exit path ordering is part of the test. */
	_exit(0);
}

static void worker(int cpu, int tracepoint_id, time_t end)
{
	unsigned long iter = 0;

	while (time(NULL) < end) {
		pid_t pid = fork();
		int status;

		if (pid < 0) {
			perror("fork");
			_exit(2);
		}
		if (!pid)
			one_child(cpu, tracepoint_id);

		while (waitpid(pid, &status, 0) < 0 && errno == EINTR)
			;
		iter++;
	}

	dprintf(2, "[worker %d] cpu=%d iterations=%lu\n",
		getpid(), cpu, iter);
	_exit(0);
}

int main(int argc, char **argv)
{
	int cpu = argc > 1 ? atoi(argv[1]) : 1;
	int workers = argc > 2 ? atoi(argv[2]) : 4;
	int duration = argc > 3 ? atoi(argv[3]) : 60;
	int tracepoint_id, ncpu;
	pid_t *pids;
	time_t end;

	if (workers < 1)
		workers = 1;
	if (duration < 1)
		duration = 1;

	ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN);
	if (cpu < 0 || cpu >= ncpu) {
		fprintf(stderr, "invalid cpu %d (online cpus: %d)\n", cpu, ncpu);
		return 1;
	}

	tracepoint_id = read_msr_tracepoint_id();
	if (tracepoint_id < 0) {
		fprintf(stderr, "cannot read msr:write_msr tracepoint id; "
			"mount tracefs/debugfs and check CONFIG_X86_MSR\n");
		return 1;
	}

	fprintf(stderr, "msr:write_msr id=%d cpu=%d workers=%d duration=%ds\n",
		tracepoint_id, cpu, workers, duration);
	fprintf(stderr, "Each child opens tracepoint(sigtrap) + "
		"hardware(cache-misses), then exits without close().\n");

	pids = calloc((size_t)workers, sizeof(*pids));
	if (!pids)
		return 1;
	end = time(NULL) + duration;

	for (int i = 0; i < workers; i++) {
		pid_t pid = fork();
		if (pid < 0) {
			perror("fork worker");
			break;
		}
		if (!pid)
			worker(cpu, tracepoint_id, end);
		pids[i] = pid;
	}

	for (int i = 0; i < workers; i++)
		if (pids[i] > 0)
			waitpid(pids[i], NULL, 0);
	free(pids);
	return 0;
}

Thanks,
Tengda


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
  2026-11-05  9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
                   ` (2 preceding siblings ...)
  2026-11-05  9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
@ 2026-11-05  9:47 ` Tengda Wu
  3 siblings, 0 replies; 5+ messages in thread
From: Tengda Wu @ 2026-11-05  9:47 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Sebastian Andrzej Siewior,
	linux-perf-users, linux-kernel



On 2026/11/5 17:30, Tengda Wu wrote:
> Two fixes for sigtrap task_work handling since the switch to always
> enqueue SIGTRAP via task_work.
> 
> The warning splats and PoCs for both patches are too long to inline in
> the commit messages. They are sent as separate replies to this cover
> letter, one per patch.
> 
> Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
> was set only after task_work_add(), so an NMI could re-enter and enqueue
> the same pending_task twice, creating a cycle in the task work list and
> unbalancing the event refcount. Claim pending_work with cmpxchg() before
> enqueuing, and clear it if the enqueue fails.
> 
> Patch 2 fixes a WARN_ON_ONCE() in perf_sigtrap() triggered by the context
> fast-swap path swapping ctx->task while a sigtrap event has pending task
> work. SIGTRAP is targeted at ctx->task, so exclude sigtrap events from
> fast-swap alongside inherit+sample-read events.
> 

This is the warning splat and PoC for patch 2
("perf/core: Disable context fast-swap for sigtrap events").

Warning observed before the fix:

[  370.271075] ------------[ cut here ]------------
[  370.271206] WARNING: kernel/events/core.c:7654 at perf_pending_task+0x2ad/0x330, CPU#1: fastswap/1414
[  370.273456] Modules linked in:
[  370.273990] CPU: 1 UID: 0 PID: 1414 Comm: fastswap Not tainted 7.3.0-next-20261027+ #4 PREEMPT(lazy)
[  370.275315] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[  370.277114] RIP: 0010:perf_pending_task+0x2ad/0x330
[  370.277989] Code: 4c 8b ab c8 fe ff ff 49 ff 8d e8 00 00 00 e9 37 fe ff ff 4c 89 ef e8 52 c2 24 00 e9 e6 fd ff ff e8 a8 9
[  370.280596] RSP: 0018:ffff88810922fe10 EFLAGS: 00010283
[  370.281502] RAX: ffff8881093b9240 RBX: ffff888108f18398 RCX: 0000000000000000
[  370.282606] RDX: ffff88811679b6c0 RSI: 0000000000000004 RDI: ffff888103287680
[  370.283691] RBP: ffff888108f18000 R08: 0000000000000001 R09: ffffed1021245fbf
[  370.284834] R10: 0000000000000003 R11: 0000000000000000 R12: 0000000000000000
[  370.285958] R13: ffff888103287600 R14: ffff888108f18260 R15: 0000000000000000
[  370.287097] FS:  00007f6f8537e600(0000) GS:ffff88826ff74000(0000) knlGS:0000000000000000
[  370.288387] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  370.289168] CR2: 00007f6f84a0c000 CR3: 00000001100a2006 CR4: 0000000000170ef0
[  370.290336] Call Trace:
[  370.290688]  <TASK>
[  370.291034]  ? count_memcg_events+0x273/0x410
[  370.291720]  task_work_run+0x13d/0x200
[  370.292365]  ? __pfx_task_work_run+0x10/0x10
[  370.292956]  irqentry_exit+0x3a8/0x700
[  370.293579]  asm_exc_page_fault+0x26/0x30
[  370.294143] RIP: 0033:0x401316
[  370.294695] Code: c6 bf 2a 01 00 00 b8 00 00 00 00 e8 14 fe ff ff c9 c3 55 48 89 e5 89 7d fc 48 89 75 f0 48 89 55 e8 8b d
[  370.297200] RSP: 002b:00007ffe7c0027f0 EFLAGS: 00010206
[  370.297963] RAX: 000000000000002e RBX: 0000000000000001 RCX: 00007f6f85254d13
[  370.299006] RDX: 00007ffe7c002800 RSI: 00007ffe7c002930 RDI: 0000000000000005
[  370.300021] RBP: 00007ffe7c0027f0 R08: 0000000000000000 R09: 00007ffe7c002b67
[  370.301043] R10: 00007f6f8537e8d0 R11: 0000000000000246 R12: 00007ffe7c0030f8
[  370.302066] R13: 0000000000401a8a R14: 00007f6f853b7aa0 R15: 0000000000403e08
[  370.303112]  </TASK>
[  370.303495] ---[ end trace 0000000000000000 ]---

PoC (not easily reproducible, needs several rounds of running):

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include "perf_event.h"
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static volatile sig_atomic_t sigtrap_count;

static int perf_open(struct perf_event_attr *attr, pid_t pid, int cpu)
{
	return syscall(__NR_perf_event_open, attr, pid, cpu, -1,
		       PERF_FLAG_FD_CLOEXEC);
}

static void sigtrap_handler(int sig, siginfo_t *info, void *ctx)
{
	(void)sig;
	(void)info;
	(void)ctx;

	sigtrap_count++;
}

static int set_cpu(int cpu)
{
	cpu_set_t set;

	CPU_ZERO(&set);
	CPU_SET(cpu, &set);

	return sched_setaffinity(0, sizeof(set), &set);
}

static int read_msr_tracepoint_id(void)
{
	static const char * const paths[] = {
		"/sys/kernel/tracing/events/msr/write_msr/id",
		"/sys/kernel/debug/tracing/events/msr/write_msr/id",
	};
	char buf[64];
	unsigned int i;

	for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
		int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
		ssize_t n;

		if (fd < 0)
			continue;

		n = read(fd, buf, sizeof(buf) - 1);
		close(fd);

		if (n <= 0)
			continue;

		buf[n] = '\0';
		return (int)strtol(buf, NULL, 10);
	}

	return -1;
}

/*
 * Open inherited MSR tracepoint event.
 *
 * The event is created by the worker. Forked children inherit
 * the event and get their own child perf context.
 */
static int open_msr_sigtrap_event(int tracepoint_id, int cpu)
{
	struct perf_event_attr attr;

	memset(&attr, 0, sizeof(attr));

	attr.size = sizeof(attr);
	attr.type = PERF_TYPE_TRACEPOINT;
	attr.config = tracepoint_id;
	attr.sample_period = 64;
	attr.sample_type = PERF_SAMPLE_IP;

	attr.pinned = 1;
	attr.exclusive = 1;
	attr.exclude_idle = 1;
	attr.exclude_user = 0;
	attr.exclude_kernel = 0;

	attr.inherit = 1;
	attr.remove_on_exec = 1;
	attr.sigtrap = 1;
	attr.sig_data = 7;

	return perf_open(&attr, 0, cpu);
}

/*
 * Open inherited cache-miss event.
 */
static int open_cache_miss_event(int cpu)
{
	struct perf_event_attr attr;

	memset(&attr, 0, sizeof(attr));

	attr.size = sizeof(attr);
	attr.type = PERF_TYPE_HARDWARE;
	attr.config = PERF_COUNT_HW_CACHE_MISSES;
	attr.sample_period = 100;
	attr.sample_type = PERF_SAMPLE_IP;

	attr.pinned = 1;
	attr.exclusive = 1;
	attr.exclude_idle = 1;
	attr.exclude_user = 0;
	attr.exclude_kernel = 0;

	attr.inherit = 1;

	/* The hardware event does not own pending_task. */

	return perf_open(&attr, 0, cpu);
}

/*
 * Generate memory pressure and frequent voluntary context switches.
 */
static void workload(unsigned int ms)
{
	struct timespec start, now;
	uint64_t elapsed;
	const size_t bytes = 8 * 1024 * 1024;
	volatile unsigned char *buf;
	volatile uint64_t x = 1;
	uint32_t index = 0x12345678;

	buf = malloc(bytes);

	if (buf)
		memset((void *)buf, 0, bytes);

	clock_gettime(CLOCK_MONOTONIC, &start);

	do {
		for (unsigned int i = 0; i < 100000; i++) {
			x = x * 1664525 + 1013904223;

			index = index * 1664525 + 1013904223;

			if (buf)
				buf[(index & (bytes - 1)) & ~63U]++;

			/*
			 * Yield every 64 iterations to increase
			 * sched_out/in activity on the same CPU.
			 */
			if (!(i & 63))
				sched_yield();
		}

		clock_gettime(CLOCK_MONOTONIC, &now);

		elapsed = (uint64_t)(now.tv_sec - start.tv_sec) * 1000;

		if (now.tv_nsec >= start.tv_nsec)
			elapsed += (uint64_t)(now.tv_nsec -
					      start.tv_nsec) / 1000000;
		else
			elapsed -= (uint64_t)(start.tv_nsec -
					      now.tv_nsec) / 1000000;

	} while (elapsed < ms);

	free((void *)buf);
	(void)x;
}

/*
 * Forked child:
 *
 *   - inherits worker's perf events;
 *   - inherits worker's CPU affinity;
 *   - executes a short workload;
 *   - exits without explicitly closing perf event FDs.
 */
static void one_child(void)
{
	sigtrap_count = 0;

	workload(5);

	_exit(0);
}

/*
 * Worker:
 *
 *   1. Bind to the target CPU.
 *   2. Create perf events with inherit=1.
 *   3. Repeatedly fork short-lived children.
 *   4. Wait for child exit.
 *
 * All workers share the same CPU.
 */
static void worker(int cpu, int tracepoint_id, time_t end)
{
	struct sigaction sa;
	unsigned long iter = 0;
	int hw_fd, tp_fd;

	memset(&sa, 0, sizeof(sa));

	sa.sa_sigaction = sigtrap_handler;
	sa.sa_flags = SA_SIGINFO | SA_RESTART;
	sigemptyset(&sa.sa_mask);

	if (sigaction(SIGTRAP, &sa, NULL))
		_exit(10);

	if (set_cpu(cpu)) {
		dprintf(2, "[%d] sched_setaffinity failed: %s\n",
			getpid(), strerror(errno));
		_exit(13);
	}

	/*
	 * Open hardware event first, then tracepoint event.
	 */
	hw_fd = open_cache_miss_event(cpu);
	if (hw_fd < 0) {
		dprintf(2, "[%d] open cache-misses failed: %s\n",
			getpid(), strerror(errno));
		_exit(11);
	}

	tp_fd = open_msr_sigtrap_event(tracepoint_id, cpu);
	if (tp_fd < 0) {
		dprintf(2, "[%d] open msr tracepoint failed: %s\n",
			getpid(), strerror(errno));
		close(hw_fd);
		_exit(12);
	}

	dprintf(2,
		"[worker %d] cpu=%d hw_fd=%d tp_fd=%d started\n",
		getpid(), cpu, hw_fd, tp_fd);

	while (time(NULL) < end) {
		pid_t pid;
		int status;

		pid = fork();

		if (pid < 0) {
			if (errno == EINTR)
				continue;

			perror("fork");
			break;
		}

		if (pid == 0)
			one_child();

		/*
		 * Worker sleeps in waitpid().
		 * Child runs on the same CPU.
		 * Child exit wakes the worker.
		 */
		for (;;) {
			pid_t ret = waitpid(pid, &status, 0);

			if (ret == pid)
				break;

			if (ret < 0 && errno == EINTR)
				continue;

			if (ret < 0)
				perror("waitpid");

			break;
		}

		iter++;
	}

	dprintf(2,
		"[worker %d] cpu=%d iterations=%lu sigtrap=%d\n",
		getpid(), cpu, iter, (int)sigtrap_count);

	/*
	 * Keep the parent events alive throughout the workload.
	 * Child inherited events are released through child exit.
	 */
	close(hw_fd);
	close(tp_fd);

	_exit(0);
}

int main(int argc, char **argv)
{
	int cpu = argc > 1 ? atoi(argv[1]) : 1;
	int workers = argc > 2 ? atoi(argv[2]) : 8;
	int duration = argc > 3 ? atoi(argv[3]) : 60;

	int tracepoint_id;
	int ncpu;
	int created = 0;

	pid_t *pids;
	time_t end;

	if (workers < 1)
		workers = 1;

	if (duration < 1)
		duration = 1;

	ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN);

	if (cpu < 0 || cpu >= ncpu) {
		fprintf(stderr,
			"invalid cpu %d (online cpus: %d)\n",
			cpu, ncpu);
		return 1;
	}

	tracepoint_id = read_msr_tracepoint_id();

	if (tracepoint_id < 0) {
		fprintf(stderr,
			"cannot read msr:write_msr tracepoint id; "
			"check tracefs/debugfs and CONFIG_X86_MSR\n");
		return 1;
	}

	pids = calloc((size_t)workers, sizeof(*pids));
	if (!pids)
		return 1;

	end = time(NULL) + duration;

	fprintf(stderr,
		"msr:write_msr id=%d cpu=%d workers=%d duration=%ds\n",
		tracepoint_id, cpu, workers, duration);

	fprintf(stderr,
		"Both perf events use inherit=1.\n");

	fprintf(stderr,
		"All workers and children are pinned to CPU %d.\n", cpu);

	fprintf(stderr,
		"Children yield frequently and exit after short workloads.\n");

	for (int i = 0; i < workers; i++) {
		pid_t pid = fork();

		if (pid < 0) {
			perror("fork worker");
			break;
		}

		if (pid == 0)
			worker(cpu, tracepoint_id, end);

		pids[created++] = pid;
	}

	for (int i = 0; i < created; i++) {
		while (waitpid(pids[i], NULL, 0) < 0) {
			if (errno == EINTR)
				continue;
			break;
		}
	}

	free(pids);

	fprintf(stderr, "All workers finished.\n");

	return 0;
}

Thanks,
Tengda


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-05  9:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05  9:30 [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05  9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
2026-11-05  9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
2026-11-05  9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05  9:47 ` Tengda Wu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®