]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
perf zstd: Fix multi-iteration decompression and error handling
authorArnaldo Carvalho de Melo <acme@redhat.com>
Sat, 2 May 2026 22:46:21 +0000 (19:46 -0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 29 May 2026 14:44:27 +0000 (11:44 -0300)
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@kernel.org # Running on a local machine
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Assisted-by: Claude:claude-opus-4.6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/zstd.c

index ecda9deb53b738fa9964d662907ab771051544ba..21a0eb58597c21f96bec288246929a72ea01fb10 100644 (file)
@@ -123,14 +123,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;