[PATCH 04/28] perf zstd: Fix multi-iteration decompression and error handling

From: Arnaldo Carvalho de Melo

Date: Sat May 09 2026 - 23:36:33 EST


From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>

zstd_decompress_stream() has two bugs in its multi-iteration loop:

1. After each ZSTD_decompressStream() call, the code advances
output.dst by output.pos but doesn't reset output.pos to 0.
ZSTD interprets output.pos relative to output.dst, so the
next iteration writes at (dst + pos) + pos = dst + 2*pos,
skipping a gap and potentially writing out of bounds.

2. On ZSTD_decompressStream() error, the loop executes break
and returns output.pos (which is > 0 if some bytes were
decompressed before the error). The caller checks
!decomp_size and skips the error, silently accepting
truncated or corrupted data.

Fix both by removing the output buffer adjustment — ZSTD
correctly accumulates output.pos across calls without it.
Return 0 on decompression error so the caller detects it.
Add a no-progress guard to prevent infinite loops if the
output buffer fills before all input is consumed.

Note: the compressed event data_size is validated against
header.size by a subsequent patch in this series
("perf tools: Harden compressed event processing").

Reported-by: sashiko-bot@xxxxxxxxxx # Running on a local machine
Cc: Ian Rogers <irogers@xxxxxxxxxx>
Cc: Jiri Olsa <jolsa@xxxxxxxxxx>
Cc: Namhyung Kim <namhyung@xxxxxxxxxx>
Assisted-by: Claude Opus 4.6 (1M context) <noreply@xxxxxxxxxxxxx>
Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
---
tools/perf/util/zstd.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/tools/perf/util/zstd.c b/tools/perf/util/zstd.c
index fde9907cf4768eff..377be0505e50a493 100644
--- a/tools/perf/util/zstd.c
+++ b/tools/perf/util/zstd.c
@@ -111,14 +111,26 @@ size_t zstd_decompress_stream(struct zstd_data *data, void *src, size_t src_size
}
}
while (input.pos < input.size) {
+ size_t prev_in = input.pos;
+ size_t prev_out = output.pos;
+
ret = ZSTD_decompressStream(data->dstream, &output, &input);
if (ZSTD_isError(ret)) {
pr_err("failed to decompress (B): %zd -> %zd, dst_size %zd : %s\n",
- src_size, output.size, dst_size, ZSTD_getErrorName(ret));
- break;
+ src_size, output.pos, dst_size, ZSTD_getErrorName(ret));
+ return 0;
}
- output.dst = dst + output.pos;
- output.size = dst_size - output.pos;
+ /*
+ * Neither stream advanced — decompression is stuck.
+ * Return 0 (error) rather than partial output: perf
+ * uses ZSTD_flushStream (not ZSTD_endStream), so the
+ * stream is continuous across compressed events.
+ * Discarding unconsumed input would desynchronize the
+ * decompressor, causing the next call to produce
+ * garbage that could be misinterpreted as valid events.
+ */
+ if (input.pos == prev_in && output.pos == prev_out)
+ return 0;
}

return output.pos;
--
2.54.0