* [PATCH 1/6] perf/sched: fix memory leaks in schedstat processing
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
@ 2026-05-29 6:49 ` Wang Haoran
2026-05-29 6:50 ` [PATCH 2/6] perf/header: validate bitmap size before allocation in do_read_bitmap Wang Haoran
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:49 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
[-- Attachment #1: Type: text/plain, Size: 5991 bytes --]
>From 82a2414eac53e2052646a1c90a8eb8c03cecef22 Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:16:39 +0800
Subject: [PATCH 1/6] perf/sched: fix memory leaks in schedstat processing
perf_sched__process_schedstat() allocates a schedstat_cpu (or
schedstat_domain) struct and its embedded data pointer, but fails to
free either when the data pointer allocation fails or when the
after_workload_flag path discards the temporary struct after diffing.
free_schedstat() walks the cpu_head list and frees each node but
omits the cpu_data and domain_data pointers allocated inside each
node, leaking them on every normal exit path.
Fix all three cases:
- free temp on zalloc failure of the inner data pointer
- free temp and its data pointer after store_schedstat_*_diff()
- free cpu_data/domain_data inside free_schedstat()
Fixes: <schedstat processing code>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
tools/perf/builtin-sched.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 3f509cfdd..ab4c9ffa4 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -4413,8 +4413,10 @@ static int perf_sched__process_schedstat(const struct perf_tool *tool __maybe_un
return -ENOMEM;
temp->cpu_data = zalloc(sizeof(*temp->cpu_data));
- if (!temp->cpu_data)
+ if (!temp->cpu_data) {
+ free(temp);
return -ENOMEM;
+ }
memcpy(temp->cpu_data, &event->schedstat_cpu, sizeof(*temp->cpu_data));
@@ -4439,6 +4441,8 @@ static int perf_sched__process_schedstat(const struct perf_tool *tool __maybe_un
domain_second_pass = list_first_entry(&cpu_second_pass->domain_head,
struct schedstat_domain, domain_list);
store_schedstat_cpu_diff(temp);
+ free(temp->cpu_data);
+ free(temp);
}
} else if (event->header.type == PERF_RECORD_SCHEDSTAT_DOMAIN) {
struct schedstat_cpu *cpu_tail;
@@ -4448,8 +4452,10 @@ static int perf_sched__process_schedstat(const struct perf_tool *tool __maybe_un
return -ENOMEM;
temp->domain_data = zalloc(sizeof(*temp->domain_data));
- if (!temp->domain_data)
+ if (!temp->domain_data) {
+ free(temp);
return -ENOMEM;
+ }
memcpy(temp->domain_data, &event->schedstat_domain, sizeof(*temp->domain_data));
@@ -4458,6 +4464,8 @@ static int perf_sched__process_schedstat(const struct perf_tool *tool __maybe_un
list_add_tail(&temp->domain_list, &cpu_tail->domain_head);
} else {
store_schedstat_domain_diff(temp);
+ free(temp->domain_data);
+ free(temp);
domain_second_pass = list_next_entry(domain_second_pass, domain_list);
}
}
@@ -4473,9 +4481,11 @@ static void free_schedstat(struct list_head *head)
list_for_each_entry_safe(cptr, n2, head, cpu_list) {
list_for_each_entry_safe(dptr, n1, &cptr->domain_head, domain_list) {
list_del_init(&dptr->domain_list);
+ free(dptr->domain_data);
free(dptr);
}
list_del_init(&cptr->cpu_list);
+ free(cptr->cpu_data);
free(cptr);
}
}
--
2.53.0
---
ASan output on perf 7.0.6 (unpatched) with the attached PoC:
0xb0 [0]: failed to process type: 1685713920 [Invalid argument]
=================================================================
==55915==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 73 byte(s) in 1 object(s) allocated from:
#0 0x7f86f552b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
#1 0x7f86f4096e6e in __vasprintf_internal libio/vasprintf.c:116
#2 0x7f86f4143172 in ___asprintf_chk debug/asprintf_chk.c:34
#3 0x6072f7e48ee3 in asprintf /usr/include/x86_64-linux-gnu/bits/stdio2.h:206
#4 0x6072f7e48ee3 in astrcat
#5 0x6072f7e48ee3 in parse_options_subcommand
#6 0x6072f7de3ef0 in cmd_sched (perf+0x33eef0) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x6072f7e2887f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x6072f7c9b836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#9 0x7f86f402a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#10 0x7f86f402a717 in __libc_start_main_impl ../csu/libc-start.c:360
#11 0x6072f7ca3754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
Objects leaked above:
0x7bf6f33e0800 (73 bytes)
Direct leak of 72 byte(s) in 1 object(s) allocated from:
#0 0x7f86f552b40f in calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:74
#1 0x6072f7dcfe52 in perf_sched__process_schedstat (perf+0x32ae52) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#2 0x6072f80fcdc2 in perf_session__process_user_event (perf+0x657dc2) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#3 0x6072f8101af8 in process_simple (perf+0x65caf8) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#4 0x6072f8103865 in reader__read_event (perf+0x65e865) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#5 0x6072f810421d in perf_session__process_events (perf+0x65f21d) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#6 0x6072f7de9f97 in cmd_sched (perf+0x344f97) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x6072f7e2887f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x6072f7c9b836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#9 0x7f86f402a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#10 0x7f86f402a717 in __libc_start_main_impl ../csu/libc-start.c:360
#11 0x6072f7ca3754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
Objects leaked above:
0x7bf6f33e24e0 (72 bytes)
SUMMARY: AddressSanitizer: 145 byte(s) leaked in 2 allocation(s).
[-- Attachment #2: crash_err234_iter50.data --]
[-- Type: application/octet-stream, Size: 4753 bytes --]
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 2/6] perf/header: validate bitmap size before allocation in do_read_bitmap
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
2026-05-29 6:49 ` [PATCH 1/6] perf/sched: fix memory leaks in schedstat processing Wang Haoran
@ 2026-05-29 6:50 ` Wang Haoran
2026-05-29 6:50 ` [PATCH 3/6] perf/header: reject data offset beyond file size Wang Haoran
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:50 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
[-- Attachment #1: Type: text/plain, Size: 7642 bytes --]
>From 3514ed156b02bdbbc9b37bf7a4b8cb8ee5e7e402 Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:16:53 +0800
Subject: [PATCH 2/6] perf/header: validate bitmap size before allocation in
do_read_bitmap
do_read_bitmap() reads a u64 size from the file and passes it directly
to bitmap_zalloc(), which takes an int. If size exceeds INT_MAX the
truncated int value produces a tiny allocation while the subsequent
loop reads BITS_TO_U64(size) u64 values using the original u64,
writing far beyond the allocated buffer and causing a heap overflow.
Add a bounds check that rejects any size that does not fit in an int
before the allocation.
Fixes: <do_read_bitmap>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
| 3 +++
1 file changed, 3 insertions(+)
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 9142a8ba4..e000eb9c1 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -287,6 +287,9 @@ static int do_read_bitmap(struct feat_fd *ff, unsigned long **pset, u64 *psize)
if (ret)
return ret;
+ if (size > INT_MAX)
+ return -EINVAL;
+
set = bitmap_zalloc(size);
if (!set)
return -ENOMEM;
--
2.53.0
---
ASan output on perf 7.0.6 (unpatched) with the attached PoC:
=================================================================
==55925==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6e688dfe0950 at pc 0x724890083d4c bp 0x7ffddd8621b0 sp 0x7ffddd861978
WRITE of size 8 at 0x6e688dfe0950 thread T0
#0 0x724890083d4b in read ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1017
#1 0x6111c68ee74f in read /usr/include/x86_64-linux-gnu/bits/unistd.h:32
#2 0x6111c68ee74f in ion
#3 0x6111c68ee74f in readn
#4 0x6111c6b57dcb in process_mem_topology (perf+0x603dcb) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#5 0x6111c6b51698 in perf_file_section__process (perf+0x5fd698) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#6 0x6111c6b70992 in perf_header__process_sections (perf+0x61c992) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x6111c6b72437 in perf_session__read_header (perf+0x61e437) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x6111c6bace67 in __perf_session__new (perf+0x658e67) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#9 0x6111c6898edd in cmd_sched (perf+0x344edd) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#10 0x6111c68d787f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#11 0x6111c674a836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#12 0x72488ec2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#13 0x72488ec2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#14 0x6111c6752754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
0x6e688dfe0951 is located 0 bytes after 1-byte region [0x6e688dfe0950,0x6e688dfe0951)
allocated by thread T0 here:
#0 0x72489012b40f in calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:74
#1 0x6111c6b5779d in process_mem_topology (perf+0x60379d) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#2 0x6111c6b51698 in perf_file_section__process (perf+0x5fd698) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#3 0x6111c6b70992 in perf_header__process_sections (perf+0x61c992) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#4 0x6111c6b72437 in perf_session__read_header (perf+0x61e437) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#5 0x6111c6bace67 in __perf_session__new (perf+0x658e67) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#6 0x6111c6898edd in cmd_sched (perf+0x344edd) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x6111c68d787f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x6111c674a836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#9 0x72488ec2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#10 0x72488ec2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#11 0x6111c6752754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/x86_64-linux-gnu/bits/unistd.h:32 in read
Shadow bytes around the buggy address:
0x6e688dfe0680: fa fa 00 00 fa fa 00 fa fa fa 00 00 fa fa 00 fa
0x6e688dfe0700: fa fa fa fa fa fa 00 00 fa fa 00 fa fa fa 00 00
0x6e688dfe0780: fa fa 00 fa fa fa 00 fa fa fa 00 00 fa fa 00 fa
0x6e688dfe0800: fa fa 00 00 fa fa 00 fa fa fa 00 fa fa fa 00 00
0x6e688dfe0880: fa fa 00 00 fa fa 00 fa fa fa 01 fa fa fa 00 00
=>0x6e688dfe0900: fa fa 00 04 fa fa 00 fa fa fa[fa]fa fa fa fa fa
0x6e688dfe0980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x6e688dfe0a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x6e688dfe0a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x6e688dfe0b00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x6e688dfe0b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
AddressSanitizer:DEADLYSIGNAL
=================================================================
==55925==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x6111c687a20c bp 0x7ffddd8628e0 sp 0x7ffddd862810 T0)
==55925==The signal is caused by a READ memory access.
==55925==Hint: address points to the zero page.
#0 0x6111c687a20c in show_schedstat_data (perf+0x32620c) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#1 0x6111c6899d09 in cmd_sched (perf+0x345d09) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#2 0x6111c68d787f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#3 0x6111c674a836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#4 0x72488ec2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#5 0x72488ec2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#6 0x6111c6752754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
==55925==Register values:
rax = 0x0000000000000000 rbx = 0x00006eb88dfe2710 rcx = 0x0000000000000000 rdx = 0x0000000000000000
rdi = 0x0000000000000000 rsi = 0x0000000000000000 rbp = 0x00007ffddd8628e0 rsp = 0x00007ffddd862810
r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000002 r11 = 0x0000000000000000
r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x000070188dfe0080
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (perf+0x32620c) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db) in show_schedstat_data
==55925==ABORTING
[-- Attachment #2: crash_sig6_iter840.data --]
[-- Type: application/octet-stream, Size: 4758 bytes --]
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 3/6] perf/header: reject data offset beyond file size
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
2026-05-29 6:49 ` [PATCH 1/6] perf/sched: fix memory leaks in schedstat processing Wang Haoran
2026-05-29 6:50 ` [PATCH 2/6] perf/header: validate bitmap size before allocation in do_read_bitmap Wang Haoran
@ 2026-05-29 6:50 ` Wang Haoran
2026-05-29 6:50 ` [PATCH 4/6] perf/header: add bounds check for domain index in process_cpu_domain_info Wang Haoran
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:50 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
[-- Attachment #1: Type: text/plain, Size: 3413 bytes --]
>From cc2ff328f62f766f74de038347d88ee2dc75f78d Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:17:07 +0800
Subject: [PATCH 3/6] perf/header: reject data offset beyond file size
A crafted perf.data file can set data.offset to a value larger than
the actual file size. perf then calls mmap() with that file offset,
which succeeds but maps a region entirely past the end of the file.
Any subsequent access to the mapped memory triggers SIGBUS.
Add a fstat() check in perf_file_header__read() that rejects files
whose claimed data.offset exceeds the real file size.
Fixes: <perf_file_header__read>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
| 9 +++++++++
1 file changed, 9 insertions(+)
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index e000eb9c1..f1a1831cf 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -4335,6 +4335,15 @@ int perf_file_header__read(struct perf_file_header *header,
return -1;
}
+ {
+ struct stat st;
+
+ if (fstat(fd, &st) == 0 && header->data.offset > (u64)st.st_size) {
+ pr_err("Perf file header corrupt: data offset beyond file size\n");
+ return -1;
+ }
+ }
+
if (header->size != sizeof(*header)) {
/* Support the previous format */
if (header->size == offsetof(typeof(*header), adds_features))
--
2.53.0
---
ASan output on perf 7.0.6 (unpatched) with the attached PoC:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==55933==ERROR: AddressSanitizer: BUS on unknown address (pc 0x5d35069296b4 bp 0x7fffd2df9100 sp 0x7fffd2df90a0 T0)
==55933==The signal is caused by a READ memory access.
==55933==Hint: this fault was caused by a dereference of a high value address (see register values below). Disassemble the provided pc to learn which register was used.
#0 0x5d35069296b4 in reader__read_event (perf+0x65e6b4) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#1 0x5d350692a21d in perf_session__process_events (perf+0x65f21d) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#2 0x5d350660ff97 in cmd_sched (perf+0x344f97) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#3 0x5d350664e87f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#4 0x5d35064c1836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#5 0x7f111f42a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#6 0x7f111f42a717 in __libc_start_main_impl ../csu/libc-start.c:360
#7 0x5d35064c9754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
==55933==Register values:
rax = 0x0000000000000007 rbx = 0x00007b111d54bd90 rcx = 0x0000000000000001 rdx = 0x0000000000000000
rdi = 0x00007b111d54bdd8 rsi = 0x00007ce11e5e0080 rbp = 0x00007fffd2df9100 rsp = 0x00007fffd2df90a0
r8 = 0x0000000000000f68 r9 = 0x0000000000000000 r10 = 0x00007ce11e5e0080 r11 = 0x0000000000000246
r12 = 0x00007f111ebb7f68 r13 = 0x00000000000103c8 r14 = 0x00007f111ebb7f6e r15 = 0x00007ce11e5e0080
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: BUS (perf+0x65e6b4) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db) in reader__read_event
==55933==ABORTING
[-- Attachment #2: crash_sig7_iter210.data --]
[-- Type: application/octet-stream, Size: 4760 bytes --]
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 4/6] perf/header: add bounds check for domain index in process_cpu_domain_info
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
` (2 preceding siblings ...)
2026-05-29 6:50 ` [PATCH 3/6] perf/header: reject data offset beyond file size Wang Haoran
@ 2026-05-29 6:50 ` Wang Haoran
2026-05-29 6:50 ` [PATCH 5/6] perf/sched: replace list_first_entry with list_first_entry_or_null Wang Haoran
2026-05-29 6:50 ` [PATCH 6/6] subcmd: fix memory leak in parse_options_subcommand Wang Haoran
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:50 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
[-- Attachment #1: Type: text/plain, Size: 3569 bytes --]
>From 6558dce0d11d81872d73655bc8290cfb1dc499b2 Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:17:21 +0800
Subject: [PATCH 4/6] perf/header: add bounds check for domain index in
process_cpu_domain_info
process_cpu_domain_info() reads a domain index from the file and uses
it directly as an array index into cd_map[cpu]->domains[], which has
max_sched_domains entries. A crafted file can supply a domain value
>= max_sched_domains, causing an out-of-bounds write.
Add a bounds check that rejects domain values outside the valid range.
Free the just-allocated d_info before returning to avoid a memory leak.
Fixes: <process_cpu_domain_info>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
| 5 +++++
1 file changed, 5 insertions(+)
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index f1a1831cf..6281e97ee 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -3670,6 +3670,11 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused
if (!d_info)
return -1;
+ if (domain >= max_sched_domains) {
+ free(d_info);
+ return -1;
+ }
+
assert(cd_map[cpu]->domains[domain] == NULL);
cd_map[cpu]->domains[domain] = d_info;
d_info->domain = domain;
--
2.53.0
---
ASan output on perf 7.0.6 (unpatched) with the attached PoC:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==55941==ERROR: AddressSanitizer: SEGV on unknown address 0x729c3fbe0790 (pc 0x5e98934d9c97 bp 0x7ffd2f046790 sp 0x7ffd2f046650 T0)
==55941==The signal is caused by a WRITE memory access.
#0 0x5e98934d9c97 in process_cpu_domain_info (perf+0x610c97) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#1 0x5e98934c6698 in perf_file_section__process (perf+0x5fd698) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#2 0x5e98934e5992 in perf_header__process_sections (perf+0x61c992) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#3 0x5e98934e7437 in perf_session__read_header (perf+0x61e437) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#4 0x5e9893521e67 in __perf_session__new (perf+0x658e67) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#5 0x5e989320dedd in cmd_sched (perf+0x344edd) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#6 0x5e989324c87f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x5e98930bf836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x7669c0a2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#9 0x7669c0a2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#10 0x5e98930c7754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
==55941==Register values:
rax = 0x0000000050000000 rbx = 0x00007269be7f0a50 rcx = 0x00007289bfbe0970 rdx = 0x0000000000000020
rdi = 0x0000729c3fbe0790 rsi = 0x00007299bfbe07c0 rbp = 0x00007ffd2f046790 rsp = 0x00007ffd2f046650
r8 = 0x00000e5137f7c137 r9 = 0x0000000000000000 r10 = 0x00000000000000dc r11 = 0x00007669c24acfec
r12 = 0x00007269be7f0aa0 r13 = 0x0000000000000000 r14 = 0x00007299bfbe07c0 r15 = 0x00007269be7f09b0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (perf+0x610c97) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db) in process_cpu_domain_info
==55941==ABORTING
[-- Attachment #2: crash_sig11_iter2.data --]
[-- Type: application/octet-stream, Size: 4755 bytes --]
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 5/6] perf/sched: replace list_first_entry with list_first_entry_or_null
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
` (3 preceding siblings ...)
2026-05-29 6:50 ` [PATCH 4/6] perf/header: add bounds check for domain index in process_cpu_domain_info Wang Haoran
@ 2026-05-29 6:50 ` Wang Haoran
2026-05-29 6:50 ` [PATCH 6/6] subcmd: fix memory leak in parse_options_subcommand Wang Haoran
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:50 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
>From f66a328ea7a6832689b8d19f4643f31b8caf1e28 Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:18:07 +0800
Subject: [PATCH 5/6] perf/sched: replace list_first_entry with
list_first_entry_or_null
list_first_entry() is unsafe when called on a potentially empty list:
it computes container_of() on the list head itself and returns a
garbage pointer rather than NULL, so any NULL check on the result is
dead code.
get_all_cpu_stats() and show_schedstat_data() call list_first_entry()
on lists that are populated from user-controlled perf.data content,
making them reachable via crafted input. Replace every such call with
list_first_entry_or_null() and add the corresponding NULL guards.
Fixes: <get_all_cpu_stats / show_schedstat_data>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
tools/perf/builtin-sched.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index ab4c9ffa4..55391f0b1 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -4170,7 +4170,7 @@ static void summarize_schedstat_domain(struct schedstat_domain *summary_domain,
*/
static int get_all_cpu_stats(struct list_head *head)
{
- struct schedstat_cpu *cptr = list_first_entry(head, struct schedstat_cpu, cpu_list);
+ struct schedstat_cpu *cptr = list_first_entry_or_null(head, struct schedstat_cpu, cpu_list);
struct schedstat_cpu *summary_head = NULL;
struct perf_record_schedstat_domain *ds;
struct perf_record_schedstat_cpu *cs;
@@ -4212,8 +4212,11 @@ static int get_all_cpu_stats(struct list_head *head)
cnt++;
summarize_schedstat_cpu(summary_head, cptr, cnt, is_last);
- tdptr = list_first_entry(&summary_head->domain_head, struct schedstat_domain,
- domain_list);
+ tdptr = list_first_entry_or_null(&summary_head->domain_head,
+ struct schedstat_domain,
+ domain_list);
+ if (!tdptr)
+ break;
list_for_each_entry(dptr, &cptr->domain_head, domain_list) {
summarize_schedstat_domain(tdptr, dptr, cnt, is_last);
@@ -4229,7 +4232,8 @@ static int show_schedstat_data(struct list_head *head1, struct cpu_domain_map **
struct list_head *head2, struct cpu_domain_map **cd_map2,
bool summary_only)
{
- struct schedstat_cpu *cptr1 = list_first_entry(head1, struct schedstat_cpu, cpu_list);
+ struct schedstat_cpu *cptr1 =
+ list_first_entry_or_null(head1, struct schedstat_cpu, cpu_list);
struct perf_record_schedstat_domain *ds1 = NULL, *ds2 = NULL;
struct perf_record_schedstat_cpu *cs1 = NULL, *cs2 = NULL;
struct schedstat_domain *dptr1 = NULL, *dptr2 = NULL;
@@ -4250,10 +4254,14 @@ static int show_schedstat_data(struct list_head *head1, struct cpu_domain_map **
printf("\n");
printf("%-65s: ", "Time elapsed (in jiffies)");
+ if (!cptr1)
+ return -EINVAL;
jiffies1 = cptr1->cpu_data->timestamp;
printf("%11llu", jiffies1);
if (head2) {
- cptr2 = list_first_entry(head2, struct schedstat_cpu, cpu_list);
+ cptr2 = list_first_entry_or_null(head2, struct schedstat_cpu, cpu_list);
+ if (!cptr2)
+ return -EINVAL;
jiffies2 = cptr2->cpu_data->timestamp;
printf(",%11llu", jiffies2);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 6/6] subcmd: fix memory leak in parse_options_subcommand
2026-05-29 6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
` (4 preceding siblings ...)
2026-05-29 6:50 ` [PATCH 5/6] perf/sched: replace list_first_entry with list_first_entry_or_null Wang Haoran
@ 2026-05-29 6:50 ` Wang Haoran
5 siblings, 0 replies; 7+ messages in thread
From: Wang Haoran @ 2026-05-29 6:50 UTC (permalink / raw)
To: acme
Cc: peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
linux-perf-users, linux-kernel, haoranwangsec
[-- Attachment #1: Type: text/plain, Size: 3077 bytes --]
>From 9e71ffe9400fd54c4fc958b16229e5628271e4ad Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:18:33 +0800
Subject: [PATCH 6/6] subcmd: fix memory leak in parse_options_subcommand
When subcommands are present and no usage string has been provided,
parse_options_subcommand() builds a usage string via astrcat() and
stores it in usagestr[0], but never frees it. The allocation leaks
on every normal return path.
Move the buf pointer to function scope and free it before returning.
Fixes: <parse_options_subcommand>
Signed-off-by: Wang Haoran <haoranwangsec@gmail.com>
---
tools/lib/subcmd/parse-options.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 555d617c1..1eb8053e8 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -633,10 +633,10 @@ int parse_options_subcommand(int argc, const char **argv, const struct option *o
const char *const subcommands[], const char *usagestr[], int flags)
{
struct parse_opt_ctx_t ctx;
+ char *buf = NULL;
/* build usage string if it's not provided */
if (subcommands && !usagestr[0]) {
- char *buf = NULL;
astrcatf(&buf, "%s %s [<options>] {", subcmd_config.exec_name, argv[0]);
@@ -680,6 +680,7 @@ int parse_options_subcommand(int argc, const char **argv, const struct option *o
usage_with_options(usagestr, options);
}
+ free(buf);
return parse_options_end(&ctx);
}
--
2.53.0
---
ASan output on perf 7.0.6 (unpatched) with the attached PoC:
Perf file header corrupt: header overlaps attrs
incompatible file format (rerun with -v to learn more)
Perf session creation failed.
=================================================================
==55949==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 73 byte(s) in 1 object(s) allocated from:
#0 0x77ab3032b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
#1 0x77ab2ee96e6e in __vasprintf_internal libio/vasprintf.c:116
#2 0x77ab2ef43172 in ___asprintf_chk debug/asprintf_chk.c:34
#3 0x568d03932ee3 in asprintf /usr/include/x86_64-linux-gnu/bits/stdio2.h:206
#4 0x568d03932ee3 in astrcat
#5 0x568d03932ee3 in parse_options_subcommand
#6 0x568d038cdef0 in cmd_sched (perf+0x33eef0) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#7 0x568d0391287f in handle_internal_command (perf+0x38387f) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#8 0x568d03785836 in main (perf+0x1f6836) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
#9 0x77ab2ee2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#10 0x77ab2ee2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#11 0x568d0378d754 in _start (perf+0x1fe754) (BuildId: 25d667fa7a7274046cb5bcb3375c4b1074f3f6db)
Objects leaked above:
0x741b2e1e0800 (73 bytes)
SUMMARY: AddressSanitizer: 73 byte(s) leaked in 1 allocation(s).
[-- Attachment #2: crash_err255_iter46.data --]
[-- Type: application/octet-stream, Size: 4760 bytes --]
^ permalink raw reply related [flat|nested] 7+ messages in thread