Re: [PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd
From: Ian Rogers
Date: Tue Sep 15 2026 - 16:39:46 EST
On Thu, May 28, 2026 at 11:52 PM Wang Haoran <haoranwangsec@xxxxxxxxx> wrote:
>
> >From 9e71ffe9400fd54c4fc958b16229e5628271e4ad Mon Sep 17 00:00:00 2001
> From: Wang Haoran <haoranwangsec@xxxxxxxxx>
> 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
>