From c24362650f7a8573dc9a9991a034b8500af8eb19 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Thu, 30 Jul 2026 06:00:31 +0300 Subject: [PATCH 1/9] tools/power turbostat: Read complete CPU lists CPU_SUBSET_MAXCPUS accepts CPU IDs through 8191, but the sysfs and cgroup CPU-list readers stop at 1024 bytes. A fragmented cpuset or hybrid PMU CPU list can exceed that size and be truncated. initialize_cpu_set_from_sysfs() can also pass a full, non-NUL-terminated buffer to parse_cpu_str(), causing an out-of-bounds read. Additionally, initialize_cpu_set_from_sysfs() returns with the file still open on success. It is now called once per CPU for thread_siblings_list, so large systems can leak thousands of descriptors during topology discovery. Use getline() so both readers receive a complete, NUL-terminated line. Close and free the sysfs CPU-list resources on every path, and report malformed input from the correct buffer. Fixes: eb187540d13a ("tools/power turbostat: Increase CPU_SUBSET_MAXCPUS to 8192") Fixes: 58839fdbd441 ("tools/power turbostat: Process HT siblings in CPU order") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 44 +++++++++++++++------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 920694c3c1ec..cec3f2fbba45 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -6311,33 +6311,35 @@ int for_all_proc_cpus(int (func) (int)) #define PATH_EFFECTIVE_CPUS "/sys/fs/cgroup/cpuset.cpus.effective" -static char cpu_effective_str[1024]; +static char *cpu_effective_str; static int update_effective_str(bool startup) { FILE *fp; - char *pos; - char buf[1024]; + char *buf = NULL; + size_t size = 0; int ret; - if (cpu_effective_str[0] == '\0' && !startup) + if (!cpu_effective_str && !startup) return 0; fp = fopen(PATH_EFFECTIVE_CPUS, "r"); if (!fp) return 0; - pos = fgets(buf, 1024, fp); - if (!pos) + if (getline(&buf, &size, fp) < 0) err(1, "%s: file read failed", PATH_EFFECTIVE_CPUS); fclose(fp); - ret = strncmp(cpu_effective_str, buf, 1024); - if (!ret) + ret = cpu_effective_str ? strcmp(cpu_effective_str, buf) : 1; + if (!ret) { + free(buf); return 0; + } - strncpy(cpu_effective_str, buf, 1024); + free(cpu_effective_str); + cpu_effective_str = buf; return 1; } @@ -8529,13 +8531,16 @@ int add_rapl_perf_counter(int cpu, struct rapl_counter_info_t *rci, const struct return ret; } -char cpuset_buf[1024]; -int initialize_cpu_set_from_sysfs(cpu_set_t *cpu_set, char *sysfs_path, char *sysfs_file) +int initialize_cpu_set_from_sysfs(cpu_set_t *cpu_set, const char *sysfs_path, + const char *sysfs_file) { FILE *fp; + char *cpuset_buf = NULL; + size_t size = 0; char path[128]; + int ret = -1; - if (snprintf(path, 128, "%s/%s", sysfs_path, sysfs_file) > 128) + if (snprintf(path, sizeof(path), "%s/%s", sysfs_path, sysfs_file) >= (int)sizeof(path)) err(-1, "%s %s", sysfs_path, sysfs_file); fp = fopen(path, "r"); @@ -8543,19 +8548,20 @@ int initialize_cpu_set_from_sysfs(cpu_set_t *cpu_set, char *sysfs_path, char *sy warn("open %s", path); return -1; } - if (fread(cpuset_buf, sizeof(char), 1024, fp) == 0) { + if (getline(&cpuset_buf, &size, fp) < 0) { warn("read %s", sysfs_path); - goto err; + goto out; } if (parse_cpu_str(cpuset_buf, cpu_set, cpu_possible_setsize)) { - warnx("%s: cpu str malformat %s\n", sysfs_path, cpu_effective_str); - goto err; + warnx("%s: cpu str malformat %s\n", sysfs_path, cpuset_buf); + goto out; } - return 0; + ret = 0; -err: +out: fclose(fp); - return -1; + free(cpuset_buf); + return ret; } void print_cpu_set(char *s, cpu_set_t *set) From 1de8307e861759386057f8683bdf0594b2c803f0 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Thu, 13 Aug 2026 15:27:18 -0400 Subject: [PATCH 2/9] tools/power turbostat: Warn, but don't Err on out-dated perf systems Some systems in the field have perf enabled, but their kernel perf support lacks the latest L2 stats. Warn, but don't Err on those systems, and suggest how to avoid the Warn. Fixes: dd23bfe4c317 ("tools/power turbostat: Add L2 cache statistics") Reported-by: David Arcari Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index cec3f2fbba45..0c55698d6843 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -9477,8 +9477,10 @@ void perf_l2_init(void) free_fd_l2_percpu(); return; } - } else - err(-1, "%s: cpu%d: type %d", __func__, cpu, cpus[cpu].type); + } else { + warn("%s: cpu%d: type %d: Update kernel perf support or use \"--hide L2MRPS,L2%%hit\" or \"--hide cache\" or \"--no-perf\"", __func__, cpu, cpus[cpu].type); + return; + } } BIC_PRESENT(BIC_L2_MRPS); BIC_PRESENT(BIC_L2_HIT); From 148aefa949b1da358c0ebd89e61b3efd48782b82 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Wed, 2 Sep 2026 14:59:22 +0800 Subject: [PATCH 3/9] tools/power turbostat: add NULL check after calloc() Three calloc() calls in turbostat lack NULL return checks, leading to potential NULL pointer dereferences on allocation failure: 1. rapl_perf_init(): the allocated `domain_visited` is used later via memset(domain_visited, 0, ...) without checking for NULL. 2. added_perf_counters_init_(): the allocated `domain_visited` is used later via memset() and domain_visited[next_domain] without checking for NULL. 3. pmt_add_counter(): the allocated `pcounter` is dereferenced immediately via strncpy(pcounter->name, ...) without checking for NULL. Add NULL checks after each calloc(), using the same error handling style already present in each function: err(-1, ...) for rapl_perf_init(), errx(1, ...) for added_perf_counters_init_(), and return 1 for pmt_add_counter(). Signed-off-by: longlong yan Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 0c55698d6843..808f079c874c 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -8661,6 +8661,8 @@ void rapl_perf_init(void) { const unsigned int num_domains = get_rapl_num_domains(); bool *domain_visited = calloc(num_domains, sizeof(bool)); + if (!domain_visited) + err(-1, "calloc domain_visited"); rapl_counter_info_perdomain = calloc(num_domains, sizeof(*rapl_counter_info_perdomain)); if (rapl_counter_info_perdomain == NULL) @@ -10007,6 +10009,8 @@ int added_perf_counters_init_(struct perf_counter_info *pinfo) const size_t max_num_domains = MAX(topo.max_cpu_num + 1, MAX(topo.max_core_id + 1, topo.max_package_id + 1)); domain_visited = calloc(max_num_domains, sizeof(*domain_visited)); + if (!domain_visited) + errx(1, "%s: alloc %s", __func__, "domain_visited"); while (pinfo) { switch (pinfo->scope) { @@ -10398,6 +10402,8 @@ int pmt_add_counter(unsigned int guid, unsigned int seq, const char *name, enum pcounter = pmt_find_counter(*pmt_root, name); if (!pcounter) { pcounter = calloc(1, sizeof(*pcounter)); + if (!pcounter) + return 1; new_counter = true; } From 485c59972808078570d21317de8275af47a18696 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Mon, 22 Jun 2026 15:29:34 -0400 Subject: [PATCH 4/9] tools/power turbostat: Sanity check GFX%rc6 and SAM%mc6 percentages When measuring over a suspend cycle, GRX%rc6 and SAM%mc6 are observed to print as numbers much greater than 100%. Those numbers are not useful, and they mess up the columns. Detect when they are out of range, and print them as NaN. Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 808f079c874c..6a59cdcf8ba3 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -3636,7 +3636,7 @@ int format_counters(PER_THREAD_PARAMS) if (p->gfx_rc6_ms == -1) { /* detect GFX counter reset */ outp += sprintf(outp, "%s**.**", (printed++ ? delim : "")); } else { - outp += sprintf(outp, "%s%.2f", (printed++ ? delim : ""), p->gfx_rc6_ms / 10.0 / interval_float); + outp += sprintf(outp, "%s%.2f", (printed++ ? delim : ""), pct(p->gfx_rc6_ms / 10.0, interval_float)); } } @@ -3653,7 +3653,7 @@ int format_counters(PER_THREAD_PARAMS) if (p->sam_mc6_ms == -1) { /* detect GFX counter reset */ outp += sprintf(outp, "%s**.**", (printed++ ? delim : "")); } else { - outp += sprintf(outp, "%s%.2f", (printed++ ? delim : ""), p->sam_mc6_ms / 10.0 / interval_float); + outp += sprintf(outp, "%s%.2f", (printed++ ? delim : ""), pct(p->sam_mc6_ms / 10.0, interval_float)); } } From 767c64414c4c5fffd6cbc3a1cf1fb8138b28645b Mon Sep 17 00:00:00 2001 From: Len Brown Date: Thu, 30 Apr 2026 21:28:19 -0400 Subject: [PATCH 5/9] tools/power turbostat: Fix fd_perf leak on reset When turbostat notices a topology change, it frees perf file descriptors for each CPU to prepare to re-initialize. Fix an off-by-one error in that per-cpu loop that resulted in a file descriptor leak of the file for the highest numbered CPU in the system. Fixes: 67bab430f4e7 ("tools/power turbostat: Group SMI counter with APERF and MPERF") Assisted-by: GitHub Copilot (claude-sonnet-4.6) Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 6a59cdcf8ba3..8e017b4dbc6c 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -5915,7 +5915,7 @@ void free_fd_msr(void) if (!msr_counter_info) return; - for (int cpu = 0; cpu < topo.max_cpu_num; ++cpu) { + for (int cpu = 0; cpu <= topo.max_cpu_num; ++cpu) { if (msr_counter_info[cpu].fd_perf != -1) close(msr_counter_info[cpu].fd_perf); } From 5e940dd96bef6ca03b3b1b4740351237e5ec17e3 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Thu, 30 Apr 2026 21:40:36 -0400 Subject: [PATCH 6/9] tools/power turbostat: Fix CWF PMT off-by-one Fix off-by-one error causing last CPU on CWF to never get a pmt counter allocated. Fixes: 5ce1e9bbb2a1 ("tools/power turbostat: Add CPU%c1e BIC for CWF") Assisted-by: GitHub Copilot (claude-sonnet-4.6) Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 8e017b4dbc6c..795946bcfd94 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -10453,7 +10453,7 @@ void pmt_init(void) mod_num = 0; /* Relative module number for current PMT file. */ /* Open the counter for each CPU. */ - for (cpu_num = 0; cpu_num < topo.max_cpu_num;) { + for (cpu_num = 0; cpu_num <= topo.max_cpu_num;) { if (cpu_is_not_allowed(cpu_num)) goto next_loop_iter; From 11741cc90f9176746b8595beab4fec8d736ebd18 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Sun, 3 May 2026 15:23:25 -0400 Subject: [PATCH 7/9] tools/power turbostat: Fix PMT error path fd handling It is possible to leak a file descriptor if a diretory open succeeds, but the scan fails. Close the fd on error. Also, on cleanup, check for NULL fd. Assisted-by: Claude: Claude Sonnet 4.6 Fixes: 4265a86582ea ("tools/power turbostat: Add PMT directory iterator helper") Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 795946bcfd94..bb1e54a23ae8 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -2034,8 +2034,11 @@ const struct dirent *pmt_diriter_begin(struct pmt_diriter_t *iter, const char *p return NULL; num_names = scandir(pmt_root_path, &iter->namelist, pmt_telemdir_filter, pmt_telemdir_sort); - if (num_names == -1) + if (num_names == -1) { + closedir(iter->dir); + iter->dir = NULL; return NULL; + } } iter->current_name_idx = 0; @@ -2063,8 +2066,10 @@ void pmt_diriter_remove(struct pmt_diriter_t *iter) iter->num_names = 0; iter->current_name_idx = 0; - closedir(iter->dir); - iter->dir = NULL; + if (iter->dir) { + closedir(iter->dir); + iter->dir = NULL; + } } unsigned int pmt_counter_get_width(const struct pmt_counter *p) From 154b96dab83fd953b9b82c8bcfa041cd6a9d74d3 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Sun, 3 May 2026 14:17:54 -0400 Subject: [PATCH 8/9] tools/power turbostat: Fix add_counter issue if > 24 counters. We allocate space for added counters ahead of time, and we arbitrarily chose 24 added counters of each type. If a future code flow hits this limit and continues, and then removes counters, we do a phantom increment and lose track of the number of added counters. Increment the count only if there is no error. Fixes: 388e9c8134be ("tools/power turbostat: Make extensible via the --add parameter") Assisted-by: Claude: Claude Sonnet 4.6 Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index bb1e54a23ae8..9a2aa703d9d6 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -10687,10 +10687,11 @@ int add_counter(unsigned int msr_num, char *path, char *name, fprintf(stderr, "%s: %s FOUND\n", __func__, name); break; } - if (sys.added_thread_counters++ >= MAX_ADDED_THREAD_COUNTERS) { + if (sys.added_thread_counters >= MAX_ADDED_THREAD_COUNTERS) { warnx("ignoring thread counter %s", name); return -1; } + sys.added_thread_counters++; break; case SCOPE_CORE: msrp = find_msrp_by_name(sys.cp, name); @@ -10699,10 +10700,11 @@ int add_counter(unsigned int msr_num, char *path, char *name, fprintf(stderr, "%s: %s FOUND\n", __func__, name); break; } - if (sys.added_core_counters++ >= MAX_ADDED_CORE_COUNTERS) { + if (sys.added_core_counters >= MAX_ADDED_CORE_COUNTERS) { warnx("ignoring core counter %s", name); return -1; } + sys.added_core_counters++; break; case SCOPE_PACKAGE: msrp = find_msrp_by_name(sys.pp, name); @@ -10711,10 +10713,11 @@ int add_counter(unsigned int msr_num, char *path, char *name, fprintf(stderr, "%s: %s FOUND\n", __func__, name); break; } - if (sys.added_package_counters++ >= MAX_ADDED_PACKAGE_COUNTERS) { + if (sys.added_package_counters >= MAX_ADDED_PACKAGE_COUNTERS) { warnx("ignoring package counter %s", name); return -1; } + sys.added_package_counters++; break; default: warnx("ignoring counter %s with unknown scope", name); From d3eea584b56fecf5c3fba0979f0da28a6600d6a3 Mon Sep 17 00:00:00 2001 From: Len Brown Date: Sun, 24 May 2026 17:03:47 -0400 Subject: [PATCH 9/9] turbostat 2026.09.28 Signed-off-by: Len Brown --- tools/power/x86/turbostat/turbostat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 9a2aa703d9d6..cb3d571f121e 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -10627,7 +10627,7 @@ int get_and_dump_counters(void) void print_version() { - fprintf(outf, "turbostat version 2026.04.21 - Len Brown \n"); + fprintf(outf, "turbostat version 2026.09.28 - Len Brown \n"); } #define COMMAND_LINE_SIZE 2048