[PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd

Wang Haoran posted 6 patches 1 week, 3 days ago
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(-)
[PATCH 0/6] perf: fix six memory-safety vulnerabilities in sched/header/subcmd
Posted by Wang Haoran 1 week, 3 days ago
>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