From: Namhyung Kim <namhyung@kernel.org>
To: Dmitry Ilvokhin <d@ilvokhin.com>
Cc: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Nick Terrell <terrelln@fb.com>, David Sterba <dsterba@suse.com>,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
kernel-team@meta.com, Farid Zakaria <fmzakari@meta.com>,
stable@vger.kernel.org
Subject: Re: [PATCH v2 1/3] perf record: Fix multiple PERF_RECORD_COMPRESSED2 records per push
Date: Sat, 4 Jul 2026 10:08:03 -0700 [thread overview]
Message-ID: <akk98zIfF08BoADo@google.com> (raw)
In-Reply-To: <079503c01a3e28d3775947f3449cadacfa1f4117.1782743187.git.d@ilvokhin.com>
Hello,
On Tue, Jun 30, 2026 at 07:17:00AM +0000, Dmitry Ilvokhin wrote:
> With Zstd compression enabled ('perf record -z'), a single mmap push
> whose compressed output exceeds the maximum record size makes
> zstd_compress_stream_to_records() emit several PERF_RECORD_COMPRESSED2
> records back to back. record__pushfn() however rewrote only the first
> record's header to describe the whole blob as one record:
>
> event->data_size = compressed - sizeof(struct perf_record_compressed2);
> event->header.size = PERF_ALIGN(compressed, sizeof(u64));
> padding = event->header.size - compressed;
> ...
> record__write(rec, map, &pad, padding);
>
> perf_event_header::size is a __u16, so once the compressed blob no
> longer fits in it the header.size assignment truncates and 'padding'
> (size_t) underflows. write() is then handed that bogus length and fails
> with EFAULT, aborting the recording:
>
> failed to write perf data, error: Bad address
>
> The bytes that did reach the file are mis-framed, so reading it back
> cannot be decompressed.
>
> This is easy to hit with a high event rate and a large buffer, e.g.:
>
> perf record -z -F max -m 32M --per-thread -- perf test -w thloop 5 1
>
> The single-record fixup is wrong by construction: because header.size is
> 16 bits a compressed record cannot exceed 64KB, so the compressor must
> split a push into a chain of records, and the session reader already
> consumes them as such.
>
> Frame each record where it is produced instead: make
> process_comp_header() set the per-record data_size, 8-byte-align
> header.size and zero the trailing padding, and let record__pushfn()
> write the resulting blob, as the AIO path already does. Reduce
> max_record_size by sizeof(u64) so the per-record alignment padding
> cannot push header.size past its u16 field.
>
> There is no on-disk format change; a perf.data written by the fixed tool
> is still read by existing perf.
Thanks for working on this!
>
> Fixes: 208c0e168344 ("perf record: Add 8-byte aligned event type PERF_RECORD_COMPRESSED2")
> Reported-by: Farid Zakaria <fmzakari@meta.com>
> Signed-off-by: Dmitry Ilvokhin <d@ilvokhin.com>
> Cc: stable@vger.kernel.org
> ---
> tools/perf/builtin-record.c | 41 ++++++------
> .../record+zstd_comp_decomp_multi_record.sh | 64 +++++++++++++++++++
> tools/perf/util/zstd.c | 2 +-
> 3 files changed, 87 insertions(+), 20 deletions(-)
> create mode 100755 tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh
>
> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
> index 4a5eba498c02..2562c3177eae 100644
> --- a/tools/perf/builtin-record.c
> +++ b/tools/perf/builtin-record.c
> @@ -652,27 +652,14 @@ static int record__pushfn(struct mmap *map, void *to, void *bf, size_t size)
> struct record *rec = to;
>
> if (record__comp_enabled(rec)) {
> - struct perf_record_compressed2 *event = map->data;
> - size_t padding = 0;
> - u8 pad[8] = {0};
> ssize_t compressed = zstd_compress(rec->session, map, map->data,
> mmap__mmap_len(map), bf, size);
>
> if (compressed < 0)
> return (int)compressed;
>
> - bf = event;
> thread->samples++;
> -
> - /*
> - * The record from `zstd_compress` is not 8 bytes aligned, which would cause asan
> - * error. We make it aligned here.
> - */
> - event->data_size = compressed - sizeof(struct perf_record_compressed2);
> - event->header.size = PERF_ALIGN(compressed, sizeof(u64));
> - padding = event->header.size - compressed;
> - return record__write(rec, map, bf, compressed) ||
> - record__write(rec, map, &pad, padding);
> + return record__write(rec, map, map->data, compressed);
> }
>
> thread->samples++;
> @@ -1590,18 +1577,29 @@ static void record__adjust_affinity(struct record *rec, struct mmap *map)
> }
> }
>
> -static size_t process_comp_header(void *record, size_t increment)
> +/*
> + * Called once with data_size == 0 to start a record, then once with
> + * data_size == compressed payload size to finalize and 8-byte-pad it
> + * (unaligned records trip ASan in the reader).
> + */
> +static size_t process_comp_header(void *record, size_t data_size)
> {
> struct perf_record_compressed2 *event = record;
> size_t size = sizeof(*event);
>
> - if (increment) {
> - event->header.size += increment;
> - return increment;
> + if (data_size) {
> + size_t padding;
> +
> + event->data_size = data_size;
> + event->header.size = PERF_ALIGN(size + data_size, sizeof(u64));
> + padding = event->header.size - size - data_size;
> + memset(record + size + data_size, 0, padding);
> + return data_size + padding;
> }
>
> event->header.type = PERF_RECORD_COMPRESSED2;
> event->header.size = size;
> + event->data_size = 0;
>
> return size;
> }
> @@ -1610,7 +1608,12 @@ static ssize_t zstd_compress(struct perf_session *session, struct mmap *map,
> void *dst, size_t dst_size, void *src, size_t src_size)
> {
> ssize_t compressed;
> - size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed2) - 1;
> + /*
> + * Reserve space so per-record PERF_ALIGN() padding keeps header.size
> + * within u16.
> + */
> + size_t max_record_size = PERF_SAMPLE_MAX_SIZE
> + - sizeof(struct perf_record_compressed2) - sizeof(u64);
> struct zstd_data *zstd_data = &session->zstd_data;
>
> if (map && map->file)
> diff --git a/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh b/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh
> new file mode 100755
> index 000000000000..42efe7260def
> --- /dev/null
> +++ b/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh
> @@ -0,0 +1,64 @@
> +#!/bin/bash
> +# Zstd perf.data compression/decompression of multi-record data
> +
> +# SPDX-License-Identifier: GPL-2.0
Can you please remove the blank line here?
Also, this series cannot apply anymore. Please rebase onto the latest
perf-tools-next.
Thanks,
Namhyung
> +
> +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
> +recout=$(mktemp /tmp/__perf_test.zstd.rec.XXXXX)
> +injout=$(mktemp /tmp/__perf_test.zstd.inj.XXXXX)
> +perf_tool=perf
> +
> +cleanup() {
> + rm -f "${perfdata}" "${perfdata}".old "${perfdata}".decomp "${recout}" "${injout}"
> +}
> +trap cleanup EXIT TERM INT
> +
> +skip_if_no_z_record() {
> + $perf_tool record -h 2>&1 | grep -q -- '-z, --compression-level'
> +}
> +
> +collect_z_record() {
> + echo "Collecting compressed record file:"
> + [ "$(uname -m)" != s390x ] && gflag='-g'
> + $perf_tool record -o "${perfdata}" $gflag -z -F max -m 32M --per-thread -- \
> + $perf_tool test -w thloop 5 1 \
> + >/dev/null 2>"${recout}"
> +}
> +
> +check_record() {
> + echo "Checking record did not fail to write data:"
> + if grep -q "failed to write perf data" "${recout}"; then
> + cat "${recout}"
> + return 1
> + fi
> +}
> +
> +check_decompress() {
> + echo "Checking compressed file decompresses cleanly:"
> + if ! $perf_tool inject -i "${perfdata}" -o "${perfdata}".decomp 2>"${injout}"; then
> + cat "${injout}"
> + return 1
> + fi
> + if grep -Eqi "decompress|corrupt|failed to process type" "${injout}"; then
> + cat "${injout}"
> + return 1
> + fi
> +}
> +
> +skip_if_no_z_record || exit 2
> +collect_z_record
> +check_record || exit 1
> +
> +# Need >1 record, else the multi-record path wasn't exercised.
> +# Skip rather than pass/fail spuriously.
> +nr=$($perf_tool report -i "${perfdata}" --stats 2>/dev/null |
> + awk '/COMPRESSED2 events:/ { print $3 }')
> +if [ -z "${nr}" ] || [ "${nr}" -lt 2 ]; then
> + echo "less than two compressed records (${nr:-0}), skipping"
> + exit 2
> +fi
> +echo "Produced ${nr} compressed records"
> +
> +check_decompress
> +err=$?
> +exit $err
> diff --git a/tools/perf/util/zstd.c b/tools/perf/util/zstd.c
> index 57027e0ac7b6..1955fa2431d1 100644
> --- a/tools/perf/util/zstd.c
> +++ b/tools/perf/util/zstd.c
> @@ -30,7 +30,7 @@ int zstd_fini(struct zstd_data *data)
>
> ssize_t zstd_compress_stream_to_records(struct zstd_data *data, void *dst, size_t dst_size,
> void *src, size_t src_size, size_t max_record_size,
> - size_t process_header(void *record, size_t increment))
> + size_t process_header(void *record, size_t data_size))
> {
> size_t ret, size, compressed = 0;
> ZSTD_inBuffer input = { src, src_size, 0 };
> --
> 2.53.0-Meta
>
next prev parent reply other threads:[~2026-07-04 17:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-30 7:16 [PATCH v2 0/3] perf record: fix multi-record Zstd compression Dmitry Ilvokhin
2026-06-30 7:17 ` [PATCH v2 1/3] perf record: Fix multiple PERF_RECORD_COMPRESSED2 records per push Dmitry Ilvokhin
2026-06-30 7:32 ` sashiko-bot
2026-07-04 17:08 ` Namhyung Kim [this message]
2026-07-06 16:42 ` Dmitry Ilvokhin
2026-06-30 7:17 ` [PATCH v2 2/3] perf record: Avoid overrunning the zstd output buffer Dmitry Ilvokhin
2026-06-30 7:17 ` [PATCH v2 3/3] perf record: Don't store raw data on zstd compression failure Dmitry Ilvokhin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=akk98zIfF08BoADo@google.com \
--to=namhyung@kernel.org \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=d@ilvokhin.com \
--cc=dsterba@suse.com \
--cc=fmzakari@meta.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=kernel-team@meta.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=stable@vger.kernel.org \
--cc=terrelln@fb.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.