[PATCH V5 2/2] tools/perf: Use scnprintf in buffer offset calculations
From: Athira Rajeev <hidden>
Date: 2026-06-09 13:43:59
Also in:
linux-perf-users
Subsystem:
performance events subsystem, the rest · Maintainers:
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Linus Torvalds
Replace snprintf with scnprintf in buffer offset calculations to ensure the 'used' count will not exceed the "len". The current logic in perf_pmu__for_each_event uses an unconditional + 1 increment to buf_used to account for null terminators. This can cause a stack buffer overflow in the subsequent scnprintf call. When the local stack buffer buf (1024 bytes) is full, buf_used can reach 1025. This causes the subsequent remaining space calculation sizeof(buf) - buf_used to underflow. Use sub_non_neg() to see if space actually existed, and only increment the offset if remaining space is present. Changes includes: - Use sub_non_neg to check if space exists - Replacing snprintf with scnprintf to ensure the return value reflects the actual bytes written into the buffer. - Only increment buf_used by 1 if space exists - If a parameterized event uses a built-in perf keyword for its parameter name (eg, config=?), the lexer parses it as a predefined term token, which sets term->config to NULL. Add check to use parse_events__term_type_str() if term->config is NULL. Signed-off-by: Athira Rajeev <redacted> --- Changelog: v4 -> v5: Addressed review comment from Namhyung in buf_used variable to use return from scnprintf since cannot return a number greater than or equal to argument v2 -> v3: - Split the scnprintf related changes in separate patch - Handle the overflow issues and unconditional increment wrapped around sub_non_neg addressing review comment from Sashiko tools/perf/util/pmu.c | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index e765a7ffb0d6..1539960ba23b 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c@@ -2146,15 +2146,19 @@ static char *format_alias(char *buf, int len, const struct perf_pmu *pmu, pr_err("Failure to parse '%s' terms '%s': %d\n", alias->name, alias->terms, ret); parse_events_terms__exit(&terms); - snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name); + scnprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name); return buf; } - used = snprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name); + used = scnprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name); list_for_each_entry(term, &terms.terms, list) { + const char *name = term->config; + + if (!name) + name = parse_events__term_type_str(term->type_term); if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) - used += snprintf(buf + used, sub_non_neg(len, used), - ",%s=%s", term->config, + used += scnprintf(buf + used, sub_non_neg(len, used), + ",%s=%s", name, term->val.str); } parse_events_terms__exit(&terms);
@@ -2218,6 +2222,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, int ret = 0; struct hashmap_entry *entry; size_t bkt; + size_t size_rem; if (perf_pmu__is_tracepoint(pmu)) return tp_pmu__for_each_event(pmu, state, cb);
@@ -2251,17 +2256,30 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, } buf_used = strlen(buf) + 1; } + info.scale_unit = NULL; if (strlen(event->unit) || event->scale != 1.0) { - info.scale_unit = buf + buf_used; - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, - "%G%s", event->scale, event->unit) + 1; + /* Check the remaining space */ + size_rem = sub_non_neg(sizeof(buf), buf_used); + + if (size_rem > 0) { + info.scale_unit = buf + buf_used; + buf_used += scnprintf(buf + buf_used, size_rem, "%G%s", + event->scale, event->unit) + 1; + } } info.desc = event->desc; info.long_desc = event->long_desc; - info.encoding_desc = buf + buf_used; - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, - "%.*s/%s/", (int)pmu_name_len, info.pmu_name, event->terms) + 1; + info.encoding_desc = NULL; + + /* Check the remaining space */ + size_rem = sub_non_neg(sizeof(buf), buf_used); + if (size_rem > 0) { + info.encoding_desc = buf + buf_used; + buf_used += scnprintf(buf + buf_used, size_rem, "%.*s/%s/", + (int)pmu_name_len, info.pmu_name, event->terms) + 1; + } + info.str = event->terms; info.topic = event->topic; info.deprecated = perf_pmu_alias__check_deprecated(pmu, event);
@@ -2271,7 +2289,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, } if (pmu->selectable) { info.name = buf; - snprintf(buf, sizeof(buf), "%s//", pmu->name); + scnprintf(buf, sizeof(buf), "%s//", pmu->name); info.alias = NULL; info.scale_unit = NULL; info.desc = NULL;
--
2.52.0