[PATCH] tools/power turbostat: Read complete CPU lists
From: Ali Ahmet Memis
Date: Wed Jul 29 2026 - 23:08:17 EST
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@xxxxxxxxxxxxxxx
Signed-off-by: Ali Ahmet Memis <ali@xxxxxxxxxxxxxx>
---
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 920694c3c..cec3f2fbb 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)
--
2.54.0