[PATCH 03/28] perf zstd: Fix compression error path in zstd_compress_stream_to_records()

From: Arnaldo Carvalho de Melo

Date: Sat May 09 2026 - 23:35:58 EST


From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>

The error fallback does memcpy(dst, src, src_size) intending to store
uncompressed data when compression fails, but this has three bugs:

1. dst has been advanced past the record header (and potentially
past earlier compressed records), so the copy writes to the
wrong offset in the output buffer.

2. src still points to the start of the input, not to the
remaining uncompressed data at src + input.pos. On a second
or later iteration, previously compressed data would be
duplicated.

3. No check that dst_size >= src_size — if the remaining output
space is smaller, this is an out-of-bounds write.

Replace with return -1 after resetting the ZSTD compression
context via ZSTD_initCStream(), so the context is usable for
the flush retry in __cmd_record's out_child cleanup. The -1
propagates through zstd_compress() → record__pushfn() →
perf_mmap__push() to the recording loop, which breaks out and
runs the out_child flush with the reset context.

Also fix two pre-existing issues in the same function:

- Add a dst_size guard before subtracting the record header
size: if the output buffer is nearly full, the unsigned
dst_size -= size underflows to a huge value, causing
ZSTD_compressStream to write past the buffer boundary.

- Check the ZSTD_initCStream() return value and log an error
if the context reset itself fails.

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 | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/zstd.c b/tools/perf/util/zstd.c
index 57027e0ac7b658a8..fde9907cf4768eff 100644
--- a/tools/perf/util/zstd.c
+++ b/tools/perf/util/zstd.c
@@ -55,6 +55,9 @@ ssize_t zstd_compress_stream_to_records(struct zstd_data *data, void *dst, size_
while (input.pos < input.size) {
record = dst;
size = process_header(record, 0);
+ /* Output buffer full — cannot fit even the record header */
+ if (size > dst_size)
+ return -1;
compressed += size;
dst += size;
dst_size -= size;
@@ -65,8 +68,16 @@ ssize_t zstd_compress_stream_to_records(struct zstd_data *data, void *dst, size_
if (ZSTD_isError(ret)) {
pr_err("failed to compress %ld bytes: %s\n",
(long)src_size, ZSTD_getErrorName(ret));
- memcpy(dst, src, src_size);
- return src_size;
+ /*
+ * Reset so the context is usable for the flush
+ * retry in __cmd_record's out_child cleanup.
+ */
+ ret = ZSTD_initCStream(data->cstream, data->comp_level);
+ if (ZSTD_isError(ret)) {
+ pr_err("failed to reset compression context: %s\n",
+ ZSTD_getErrorName(ret));
+ }
+ return -1;
}
size = output.pos;
size = process_header(record, size);
--
2.54.0