mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] perf/core: Prevent duplicate sigtrap task work enqueue
@ 2026-10-31 11:45 Tengda Wu
  2026-11-02 16:35 ` Sebastian Andrzej Siewior
  0 siblings, 1 reply; 3+ messages in thread
From: Tengda Wu @ 2026-10-31 11:45 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 | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 457d75ea7142..f1e4e153c49a 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -10925,16 +10925,18 @@ 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)) {
+				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;
+			} 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 +10950,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] 3+ messages in thread

* Re: [PATCH] perf/core: Prevent duplicate sigtrap task work enqueue
  2026-10-31 11:45 [PATCH] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
@ 2026-11-02 16:35 ` Sebastian Andrzej Siewior
  2026-11-03  1:21   ` Tengda Wu
  0 siblings, 1 reply; 3+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-11-02 16:35 UTC (permalink / raw)
  To: Tengda Wu
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
	Ian Rogers, Adrian Hunter, James Clark, linux-perf-users,
	linux-kernel

On 2026-10-31 11:45:20 [+0000], Tengda Wu wrote:
> 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()

How was the issue identified?

Sebastian

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

* Re: [PATCH] perf/core: Prevent duplicate sigtrap task work enqueue
  2026-11-02 16:35 ` Sebastian Andrzej Siewior
@ 2026-11-03  1:21   ` Tengda Wu
  0 siblings, 0 replies; 3+ messages in thread
From: Tengda Wu @ 2026-11-03  1:21 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
	Ian Rogers, Adrian Hunter, James Clark, linux-perf-users,
	linux-kernel



On 2026/11/3 0:35, Sebastian Andrzej Siewior wrote:
> On 2026-10-31 11:45:20 [+0000], Tengda Wu wrote:
>> 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()
> 
> How was the issue identified?
> 
> Sebastian

This issue was discovered by syzkaller, and the warning and the reproducer
(named "gpt", because I used GPT to convert the syzkaller program into C code)
are as follows:

Warning
=======

# ./gpt
msr:write_msr id=2252 cpu=1 workers=4 duration=60s
Each child opens tracepoint(sigtrap) + hardware(cache-misses), then exits without close().
[   67.536731] gpt (1697) used greatest stack depth: 24952 bytes left
[   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 ... ]---


Reproducer (named "gpt")
========================

/*
 * Reproducer for the perf_trace_event_unreg -> _free_event -> put_pmu_ctx()
 * path reached from perf_pending_task() on Linux 6.6 x86.
 *
 * Run only in a disposable VM with the Intel PMU exposed to the guest.
 * The test intentionally exits short-lived children without explicitly
 * closing the perf fds: exit_files() tears them down, then exit task_work
 * can drop the pending reference held by perf_pending_task().
 */
#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] 3+ messages in thread

end of thread, other threads:[~2026-11-03  1:21 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 11:45 [PATCH] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
2026-11-02 16:35 ` Sebastian Andrzej Siewior
2026-11-03  1:21   ` 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®