mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd
@ 2026-05-29  6:49 Wang Haoran
  2026-05-29  6:49 ` [PATCH 1/6] perf/sched: fix memory leaks in schedstat processing Wang Haoran
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ 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

>From 9e71ffe9400fd54c4fc958b16229e5628271e4ad Mon Sep 17 00:00:00 2001
From: Wang Haoran <haoranwangsec@gmail.com>
Date: Thu, 28 May 2026 15:21:08 +0800
Subject: [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd

Hi, I found several vulnerabilities in perf module.

This series fixes six memory-safety bugs found in perf version 7.0.6,
in the perf sched stats subsystem and related infrastructure.  All six
were confirmed via AddressSanitizer + LeakSanitizer with crafted
perf.data files; the reproducer files are attached to the individual
patch emails.


Privilege note
==============
"perf sched stats record" requires root to capture
scheduler events from the kernel.  However, "perf sched stats report"
processes a perf.data file from disk and requires NO special privileges.
This means an unprivileged attacker can hand a crafted perf.data to any
user who runs "perf sched stats report" -- the attack surface is fully
reachable without administrator rights.

Crash-to-fix mapping
====================

  crash_err234_iter50.data  -> patch 1 (memory leaks in schedstat)
  crash_sig6_iter840.data   -> patch 2 (heap overflow via bitmap_zalloc)
  crash_sig7_iter210.data   -> patch 3 (SIGBUS via out-of-bounds mmap)
  crash_sig11_iter2.data    -> patch 4 (array OOB in domain index)
  (no poc)                  -> patch 5 (list_first_entry on empty list)
  crash_err255_iter46.data  -> patch 6 (astrcat leak in parse_options)

Vulnerability summary
=====================

1. Memory leaks in perf_sched__process_schedstat() / free_schedstat()
   (tools/perf/builtin-sched.c)

   Three distinct leak paths:
   a) When zalloc() of the inner data pointer (cpu_data or domain_data)
      fails, the outer struct is returned without freeing the parent.
   b) In the after_workload_flag=true branch the temporary struct and
      its embedded data pointer are used then discarded without being
      freed.
   c) free_schedstat() frees each cpu/domain node but not the
      cpu_data/domain_data pointers allocated inside each node.

   ASAN/LSan reports 72-144 bytes leaked per crafted event processed.

2. Heap buffer overflow via u64->int truncation in do_read_bitmap()
   (tools/perf/util/header.c)

   bitmap_zalloc() takes an int but do_read_bitmap() passes a raw u64
   read from the file.  If size > INT_MAX the int wraps to a small
   value, a tiny buffer is allocated, and the subsequent loop that
   reads BITS_TO_U64(size) u64 words from the file writes arbitrarily
   far past the end of the allocation (heap buffer overflow).

3. SIGBUS from data.offset beyond file size
   (tools/perf/util/header.c)

   A crafted perf.data can set perf_file_header.data.offset to a value
   larger than the actual file size.  mmap() succeeds because the
   kernel accepts out-of-file offsets, but any access to the mapped
   region triggers SIGBUS.  Confirmed with data.offset=0xff68=65384
   against a 4760-byte file.

4. Array out-of-bounds write via unchecked domain index
   (tools/perf/util/header.c)

   process_cpu_domain_info() reads a domain index from the file and
   uses it directly to index cd_map[cpu]->domains[], an array of
   max_sched_domains entries.  A domain value >= max_sched_domains
   causes an out-of-bounds write into adjacent heap memory.

5. list_first_entry() misuse on potentially-empty lists
   (tools/perf/builtin-sched.c)

   get_all_cpu_stats() and show_schedstat_data() call
   list_first_entry() which, unlike list_first_entry_or_null(),
   never returns NULL -- it computes container_of() on the list head
   itself when the list is empty, producing a garbage pointer.  The
   NULL checks that follow are therefore dead code.  A crafted
   perf.data that causes an empty list makes these functions dereference
   the garbage pointer.  Replaced with list_first_entry_or_null() plus
   proper NULL guards.

6. Memory leak in parse_options_subcommand()
   (tools/lib/subcmd/parse-options.c)

   When subcommands are present and no usage string has been supplied,
   parse_options_subcommand() builds a usage string with astrcat() and
   stores the pointer in usagestr[0].  The pointer is never freed,
   causing a 73-byte leak on every invocation.

Testing
=======

Affected version: perf 7.0.6

Each patch was verified with:

  $ make -C tools/perf EXTRA_CFLAGS="-fsanitize=address,leak \
      -fno-omit-frame-pointer -Wno-error=stringop-truncation" \
      -j$(nproc)

  $ ./perf sched stats report -i <crash-file>

Before the patch the corresponding crash file produced the ASAN/LSan
report listed above.  After the patch the same file is either rejected
cleanly (patches 2-4) or processed without any error report (patches
1, 5, 6).

The reproducer files (crash_*.data) are attached to the individual
patch emails; each is a minimal crafted perf.data (~4 KB) that
isolates exactly one bug.

Wang Haoran (6):
  perf/sched: fix memory leaks in schedstat processing
  perf/header: validate bitmap size before allocation in do_read_bitmap
  perf/header: reject data offset beyond file size
  perf/header: add bounds check for domain index in process_cpu_domain_info
  perf/sched: replace list_first_entry with list_first_entry_or_null
  subcmd: fix memory leak in parse_options_subcommand

 tools/lib/subcmd/parse-options.c |  3 ++-
 tools/perf/builtin-sched.c       | 32 +++++++++++++++++++++++++-------
 tools/perf/util/header.c         | 17 +++++++++++++++++
 3 files changed, 44 insertions(+), 8 deletions(-)

--
2.53.0

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

* [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
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ 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	[flat|nested] 8+ 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
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ 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>
---
 tools/perf/util/header.c | 3 +++
 1 file changed, 3 insertions(+)

diff --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	[flat|nested] 8+ 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
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ 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>
---
 tools/perf/util/header.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --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	[flat|nested] 8+ 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
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ 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>
---
 tools/perf/util/header.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --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	[flat|nested] 8+ 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
  2026-09-15 20:38 ` [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Ian Rogers
  6 siblings, 0 replies; 8+ 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	[flat|nested] 8+ 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
  2026-09-15 20:38 ` [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Ian Rogers
  6 siblings, 0 replies; 8+ 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	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd
  2026-05-29  6:49 [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Wang Haoran
                   ` (5 preceding siblings ...)
  2026-05-29  6:50 ` [PATCH 6/6] subcmd: fix memory leak in parse_options_subcommand Wang Haoran
@ 2026-09-15 20:38 ` Ian Rogers
  6 siblings, 0 replies; 8+ messages in thread
From: Ian Rogers @ 2026-09-15 20:38 UTC (permalink / raw)
  To: Wang Haoran
  Cc: acme, peterz, mingo, namhyung, mark.rutland, alexander.shishkin,
	linux-perf-users, linux-kernel

On Thu, May 28, 2026 at 11:52 PM Wang Haoran <haoranwangsec@gmail.com> wrote:
>
> >From 9e71ffe9400fd54c4fc958b16229e5628271e4ad Mon Sep 17 00:00:00 2001
> From: Wang Haoran <haoranwangsec@gmail.com>
> Date: Thu, 28 May 2026 15:21:08 +0800
> Subject: [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd
>
> Hi, I found several vulnerabilities in perf module.
>
> This series fixes six memory-safety bugs found in perf version 7.0.6,
> in the perf sched stats subsystem and related infrastructure.  All six
> were confirmed via AddressSanitizer + LeakSanitizer with crafted
> perf.data files; the reproducer files are attached to the individual
> patch emails.

Hi Wang,

Thank you for the contribution! Since there have been similar fixes in
perf-tools-next, could you rebase and resend this series. This should
automatically trigger a Sashiko review, which is currently missing.

Thanks,
Ian

> Privilege note
> ==============
> "perf sched stats record" requires root to capture
> scheduler events from the kernel.  However, "perf sched stats report"
> processes a perf.data file from disk and requires NO special privileges.
> This means an unprivileged attacker can hand a crafted perf.data to any
> user who runs "perf sched stats report" -- the attack surface is fully
> reachable without administrator rights.
>
> Crash-to-fix mapping
> ====================
>
>   crash_err234_iter50.data  -> patch 1 (memory leaks in schedstat)
>   crash_sig6_iter840.data   -> patch 2 (heap overflow via bitmap_zalloc)
>   crash_sig7_iter210.data   -> patch 3 (SIGBUS via out-of-bounds mmap)
>   crash_sig11_iter2.data    -> patch 4 (array OOB in domain index)
>   (no poc)                  -> patch 5 (list_first_entry on empty list)
>   crash_err255_iter46.data  -> patch 6 (astrcat leak in parse_options)
>
> Vulnerability summary
> =====================
>
> 1. Memory leaks in perf_sched__process_schedstat() / free_schedstat()
>    (tools/perf/builtin-sched.c)
>
>    Three distinct leak paths:
>    a) When zalloc() of the inner data pointer (cpu_data or domain_data)
>       fails, the outer struct is returned without freeing the parent.
>    b) In the after_workload_flag=true branch the temporary struct and
>       its embedded data pointer are used then discarded without being
>       freed.
>    c) free_schedstat() frees each cpu/domain node but not the
>       cpu_data/domain_data pointers allocated inside each node.
>
>    ASAN/LSan reports 72-144 bytes leaked per crafted event processed.
>
> 2. Heap buffer overflow via u64->int truncation in do_read_bitmap()
>    (tools/perf/util/header.c)
>
>    bitmap_zalloc() takes an int but do_read_bitmap() passes a raw u64
>    read from the file.  If size > INT_MAX the int wraps to a small
>    value, a tiny buffer is allocated, and the subsequent loop that
>    reads BITS_TO_U64(size) u64 words from the file writes arbitrarily
>    far past the end of the allocation (heap buffer overflow).
>
> 3. SIGBUS from data.offset beyond file size
>    (tools/perf/util/header.c)
>
>    A crafted perf.data can set perf_file_header.data.offset to a value
>    larger than the actual file size.  mmap() succeeds because the
>    kernel accepts out-of-file offsets, but any access to the mapped
>    region triggers SIGBUS.  Confirmed with data.offset=0xff68=65384
>    against a 4760-byte file.
>
> 4. Array out-of-bounds write via unchecked domain index
>    (tools/perf/util/header.c)
>
>    process_cpu_domain_info() reads a domain index from the file and
>    uses it directly to index cd_map[cpu]->domains[], an array of
>    max_sched_domains entries.  A domain value >= max_sched_domains
>    causes an out-of-bounds write into adjacent heap memory.
>
> 5. list_first_entry() misuse on potentially-empty lists
>    (tools/perf/builtin-sched.c)
>
>    get_all_cpu_stats() and show_schedstat_data() call
>    list_first_entry() which, unlike list_first_entry_or_null(),
>    never returns NULL -- it computes container_of() on the list head
>    itself when the list is empty, producing a garbage pointer.  The
>    NULL checks that follow are therefore dead code.  A crafted
>    perf.data that causes an empty list makes these functions dereference
>    the garbage pointer.  Replaced with list_first_entry_or_null() plus
>    proper NULL guards.
>
> 6. Memory leak in parse_options_subcommand()
>    (tools/lib/subcmd/parse-options.c)
>
>    When subcommands are present and no usage string has been supplied,
>    parse_options_subcommand() builds a usage string with astrcat() and
>    stores the pointer in usagestr[0].  The pointer is never freed,
>    causing a 73-byte leak on every invocation.
>
> Testing
> =======
>
> Affected version: perf 7.0.6
>
> Each patch was verified with:
>
>   $ make -C tools/perf EXTRA_CFLAGS="-fsanitize=address,leak \
>       -fno-omit-frame-pointer -Wno-error=stringop-truncation" \
>       -j$(nproc)
>
>   $ ./perf sched stats report -i <crash-file>
>
> Before the patch the corresponding crash file produced the ASAN/LSan
> report listed above.  After the patch the same file is either rejected
> cleanly (patches 2-4) or processed without any error report (patches
> 1, 5, 6).
>
> The reproducer files (crash_*.data) are attached to the individual
> patch emails; each is a minimal crafted perf.data (~4 KB) that
> isolates exactly one bug.
>
> Wang Haoran (6):
>   perf/sched: fix memory leaks in schedstat processing
>   perf/header: validate bitmap size before allocation in do_read_bitmap
>   perf/header: reject data offset beyond file size
>   perf/header: add bounds check for domain index in process_cpu_domain_info
>   perf/sched: replace list_first_entry with list_first_entry_or_null
>   subcmd: fix memory leak in parse_options_subcommand
>
>  tools/lib/subcmd/parse-options.c |  3 ++-
>  tools/perf/builtin-sched.c       | 32 +++++++++++++++++++++++++-------
>  tools/perf/util/header.c         | 17 +++++++++++++++++
>  3 files changed, 44 insertions(+), 8 deletions(-)
>
> --
> 2.53.0
>

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

end of thread, other threads:[~2026-09-15 20:38 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH 3/6] perf/header: reject data offset beyond file size 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
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
2026-09-15 20:38 ` [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd Ian Rogers

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®