From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A5EFC19CD0A; Sun, 10 May 2026 03:34:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384081; cv=none; b=iolhOGXk2CidCCjrIPun9ghWD+FgYuhOcrnNZNTDW7tDpwcG831/MmLFV/uc3YpQd2j8urFCgMWR1/rIdsnPSqYQ5CGwrcavkjRkr2zNbqKk26kEkomc45piHR4r3HexNnTc7kkOmgIJBtmQOuAKk/e9/5eHaWt21Z8IIO62kOk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384081; c=relaxed/simple; bh=GjI6Or9N8wBybD4ZEpZb3NVs1BbNqas2/5d/0tdTtXM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Lcr424BfAwGPvjsxBTw217J4HTVoYPHnTl+WiEdx6G1ASvD7bPvlQSssHzf9sdy9hZbTIb9nld5DYJ3hM4mFt06dpctz66fNXdH1+O0DfT7ROYQbboIYgaa52x3qb4nmSJ9kf6zbqvVMjydgW2FAYQHyPHZguLpddCXL1uZQ29s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=pNNFaMPT; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="pNNFaMPT" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C32ADC2BCB8; Sun, 10 May 2026 03:34:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384081; bh=GjI6Or9N8wBybD4ZEpZb3NVs1BbNqas2/5d/0tdTtXM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pNNFaMPTatR+2NEHMpHmlhcItsx2KdOp0heiuWVw4FTw3Qky0lw0wugz7GSkjzdph QoUzDKFvRbGCV0otvwewl2LhwkE0kvDGbOHttU4PQK10kqkchAyt6XyBgGZr7p9tAI 9nNz/0sxadVT+o9R6NPxB2oDsNcM5HNEKt0qLWKZnKSX1G+4jqbc5oKo66+rErIjaI J1lFm5CsLbO/KM/Ubsf7p9pq8W2Bo5eLjHHRsel8Ly4R7EPdk+iJK8wFMO4kqGO/m0 IwLyJxCUTzCMKJWcwiARLJWRXZefHsqx+ZZxT4gAYnl5TLw4MDdbpgq47seQo079jI t/ypMOgXpnMkg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 01/28] perf session: Add minimum event size validation table Date: Sun, 10 May 2026 00:33:52 -0300 Message-ID: <20260510033424.255812-2-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Add a per-type minimum size table (perf_event__min_size[]) and enforce it before swap and processing, so that both cross-endian and native-endian paths are protected from accessing fields past the event boundary. The table uses offsetof() for types with trailing variable-length fields (filenames, strings, msg arrays) and sizeof() for fixed-size types. Zero entries mean no minimum beyond the 8-byte header already enforced by the reader. Undersized events are skipped with a warning in process_event and rejected in peek_event =E2=80=94 both checked before the swap handler runs, preventing OOB access on crafted event fields. Also guard event_swap() against crafted event types >=3D PERF_RECORD_HEADER_MAX to prevent OOB reads on the perf_event__swap_ops[] array. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index fe0de2a0277f09f9..aae0651fb6f025a1 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1759,10 +1759,88 @@ int perf_session__deliver_synth_attr_event(struct p= erf_session *session, return perf_session__deliver_synth_event(session, &ev.ev, NULL); } =20 +/* + * Minimum event sizes indexed by type. Checked before swap and + * processing so that both cross-endian and native-endian paths + * are protected from accessing fields past the event boundary. + * Zero means no minimum beyond the 8-byte header (already + * enforced by the reader). + */ +static const u32 perf_event__min_size[PERF_RECORD_HEADER_MAX] =3D { + /* + * offsetof() for types with a trailing variable-length string + * (filename, comm, path, name, msg): sizeof() includes a + * PATH_MAX or fixed-size array, but valid events only need + * the fixed fields. Null-termination is checked separately. + * + * PERF_RECORD_SAMPLE is omitted: all64_swap is bounded by + * header.size, and the internal layout varies by sample_type + * so a fixed minimum is not meaningful. + */ + [PERF_RECORD_MMAP] =3D offsetof(struct perf_record_mmap, filename), + [PERF_RECORD_LOST] =3D sizeof(struct perf_record_lost), + [PERF_RECORD_COMM] =3D offsetof(struct perf_record_comm, comm), + [PERF_RECORD_EXIT] =3D sizeof(struct perf_record_fork), + [PERF_RECORD_THROTTLE] =3D sizeof(struct perf_record_throttle), + [PERF_RECORD_UNTHROTTLE] =3D sizeof(struct perf_record_throttle), + [PERF_RECORD_FORK] =3D sizeof(struct perf_record_fork), + /* + * The kernel dynamically sizes PERF_RECORD_READ based on + * attr.read_format =E2=80=94 the minimum has just pid + tid + value. + */ + [PERF_RECORD_READ] =3D offsetof(struct perf_record_read, time_enabled), + [PERF_RECORD_MMAP2] =3D offsetof(struct perf_record_mmap2, filename), + [PERF_RECORD_LOST_SAMPLES] =3D sizeof(struct perf_record_lost_samples), + [PERF_RECORD_AUX] =3D sizeof(struct perf_record_aux), + [PERF_RECORD_ITRACE_START] =3D sizeof(struct perf_record_itrace_start), + [PERF_RECORD_SWITCH] =3D sizeof(struct perf_event_header), + [PERF_RECORD_SWITCH_CPU_WIDE] =3D sizeof(struct perf_record_switch), + [PERF_RECORD_NAMESPACES] =3D sizeof(struct perf_record_namespaces), + [PERF_RECORD_CGROUP] =3D offsetof(struct perf_record_cgroup, path), + [PERF_RECORD_TEXT_POKE] =3D sizeof(struct perf_record_text_poke_event), + [PERF_RECORD_KSYMBOL] =3D offsetof(struct perf_record_ksymbol, name), + [PERF_RECORD_BPF_EVENT] =3D sizeof(struct perf_record_bpf_event), + [PERF_RECORD_HEADER_ATTR] =3D sizeof(struct perf_event_header) + PERF_A= TTR_SIZE_VER0, + [PERF_RECORD_HEADER_EVENT_TYPE] =3D sizeof(struct perf_record_header_ev= ent_type), + [PERF_RECORD_HEADER_TRACING_DATA] =3D sizeof(struct perf_record_header_tr= acing_data), + [PERF_RECORD_AUX_OUTPUT_HW_ID] =3D sizeof(struct perf_record_aux_output= _hw_id), + [PERF_RECORD_AUXTRACE_INFO] =3D sizeof(struct perf_record_auxtrace_info= ), + [PERF_RECORD_AUXTRACE] =3D sizeof(struct perf_record_auxtrace), + [PERF_RECORD_AUXTRACE_ERROR] =3D offsetof(struct perf_record_auxtrace_e= rror, msg), + [PERF_RECORD_THREAD_MAP] =3D sizeof(struct perf_record_thread_map), + /* Smallest valid variant is RANGE_CPUS: header(8) + type(2) + range(6) */ + [PERF_RECORD_CPU_MAP] =3D sizeof(struct perf_event_header) + + sizeof(__u16) + + sizeof(struct perf_record_range_cpu_map), + [PERF_RECORD_STAT_CONFIG] =3D sizeof(struct perf_record_stat_config), + [PERF_RECORD_STAT] =3D sizeof(struct perf_record_stat), + [PERF_RECORD_STAT_ROUND] =3D sizeof(struct perf_record_stat_round), + /* Union inflates sizeof; use fixed header fields as minimum */ + [PERF_RECORD_EVENT_UPDATE] =3D offsetof(struct perf_record_event_update= , scale), + [PERF_RECORD_TIME_CONV] =3D offsetof(struct perf_record_time_conv, tim= e_cycles), + [PERF_RECORD_ID_INDEX] =3D sizeof(struct perf_record_id_index), + [PERF_RECORD_HEADER_BUILD_ID] =3D sizeof(struct perf_record_header_buil= d_id), + [PERF_RECORD_HEADER_FEATURE] =3D sizeof(struct perf_record_header_featu= re), + [PERF_RECORD_COMPRESSED2] =3D sizeof(struct perf_record_compressed2), + [PERF_RECORD_BPF_METADATA] =3D sizeof(struct perf_record_bpf_metadata), + [PERF_RECORD_CALLCHAIN_DEFERRED] =3D sizeof(struct perf_event_header) + = sizeof(__u64), + /* + * SCHEDSTAT events have a version-dependent union after the + * fixed header fields; the minimum is the base (pre-union) + * portion so old and new versions both pass. + */ + [PERF_RECORD_SCHEDSTAT_CPU] =3D offsetof(struct perf_record_schedstat_c= pu, v15), + [PERF_RECORD_SCHEDSTAT_DOMAIN] =3D offsetof(struct perf_record_schedsta= t_domain, v15), +}; + static void event_swap(union perf_event *event, bool sample_id_all) { perf_event__swap_op swap; =20 + /* Prevent OOB read on perf_event__swap_ops[] from crafted type */ + if (event->header.type >=3D PERF_RECORD_HEADER_MAX) + return; + swap =3D perf_event__swap_ops[event->header.type]; if (swap) swap(event, sample_id_all); @@ -1780,6 +1858,20 @@ int perf_session__peek_event(struct perf_session *se= ssion, off_t file_offset, if (session->one_mmap && !session->header.needs_swap) { event =3D file_offset - session->one_mmap_offset + session->one_mmap_addr; + + /* Every event must at least contain its own header */ + if (event->header.size < sizeof(struct perf_event_header)) + return -1; + + /* Reject undersized events on the native-endian fast path */ + if (event->header.type < PERF_RECORD_HEADER_MAX) { + u32 min_sz =3D perf_event__min_size[event->header.type]; + + if (min_sz && event->header.size < min_sz) { + *event_ptr =3D event; + return -1; + } + } goto out_parse_sample; } =20 @@ -1810,6 +1902,20 @@ int perf_session__peek_event(struct perf_session *se= ssion, off_t file_offset, if (readn(fd, buf, rest) !=3D (ssize_t)rest) return -1; =20 + /* Reject undersized events before swapping */ + if (event->header.type < PERF_RECORD_HEADER_MAX) { + u32 min_sz =3D perf_event__min_size[event->header.type]; + + if (min_sz && event->header.size < min_sz) { + pr_warning("WARNING: peek_event: %s event size %u too small (min %u)\n", + perf_event__name(event->header.type), + event->header.size, min_sz); + /* Expose so peek_events can advance past it */ + *event_ptr =3D event; + return -1; + } + } + if (session->header.needs_swap) event_swap(event, evlist__sample_id_all(session->evlist)); =20 @@ -1860,6 +1966,21 @@ static s64 perf_session__process_event(struct perf_s= ession *session, const struct perf_tool *tool =3D session->tool; int ret; =20 + if (event->header.type < PERF_RECORD_HEADER_MAX) { + u32 min_sz =3D perf_event__min_size[event->header.type]; + + /* + * Skip rather than abort: a crafted file may have + * isolated bad events among otherwise valid data. + */ + if (min_sz && event->header.size < min_sz) { + pr_warning("WARNING: %s event size %u too small (min %u), skipping\n", + perf_event__name(event->header.type), + event->header.size, min_sz); + return 0; + } + } + if (session->header.needs_swap) event_swap(event, evlist__sample_id_all(evlist)); =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 608501F0995; Sun, 10 May 2026 03:34:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384086; cv=none; b=XHw4NFJ6thZzRDsmGLgO1YKcllGnCRSsjkE6aND7SODjtyoCu2qDeVT/znqRTHls7NCllA8zSAqQEHUdvzDc9WVKGWr8I2h8RLGdzsWyauDWSpQtOZewTOOGF519Xu+Jmxbk1uNRrqFtXynhZ8FXMa+XJI4gXQz3c7l+dleLXOQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384086; c=relaxed/simple; bh=8Z3U7z7l7mbeyZbEtwZji0Na/j7Xf3Vx+97iUGbglmo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=VRgIDmc4Fk59XU+t1wZn1cQl3DBw+bwyN9P9OA/gyZ38L+9q31yj5U9fT00mXAOWsgbR8VBMPPiBSndoaPU/i4hssp3yxbGJ5IYyJno0NraFfzyuFixtiMxB21x9aqzfV260hXoS2ypCTIUaR+AfU4bV1Grog59KKHsT0+mGP40= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PTamJK4X; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PTamJK4X" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0F569C2BCF7; Sun, 10 May 2026 03:34:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384085; bh=8Z3U7z7l7mbeyZbEtwZji0Na/j7Xf3Vx+97iUGbglmo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PTamJK4XB4wxqk0PJjgefrx2mUHTI4hEC2eeyxyWuDKCJ0neo/EX+rosM37FJ4/jo eRPwXmVdEK+RrOsEMcSso3ugBgTc4WM2KfzxpGtfFvvczmJvqSu7pqaNOW75LLjTPM 5QrqUn/de9gC1w5iwBce1XQY+CcQCOg49A2Eoa76uln4/bRkzRu8OnVCkgpdkV57vi zkC9TAG2F3wBJClhXqwdvnp5KW+GXm/tiHFdHCEHoljTq2xe1Ed51/bSDhOWNIb3+N fVuDpWssDAS7lwsgIzlnzOS1fpFlT1FhGz/wBZ3yTEOa8b+mz/C4AWHfxREkJRViOP F0d+IW9IV65wQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 02/28] perf tools: Fix event_contains() macro to verify full field extent Date: Sun, 10 May 2026 00:33:53 -0300 Message-ID: <20260510033424.255812-3-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo event_contains() checked whether a field's start offset was within the event (header.size > offsetof), but not whether the full field fit. A crafted event with header.size =3D offsetof(field) + 1 would pass the check, but an 8-byte access (bswap_64, direct read) would overrun the event boundary by up to 7 bytes. Fix the macro to verify the complete field: header.size >=3D offsetof(field) + sizeof(field) Also update all callers that check event_contains(time_cycles) but access later fields (time_mask, cap_user_time_zero, cap_user_time_short) to check for cap_user_time_short =E2=80=94 the last field accessed =E2=80=94 so the entire extended block is verified: tsc.c, arm-spe.c, cs-etm.c, jitdump.c. Note: session.c's perf_event__time_conv_swap() also guards on time_cycles but accesses time_mask =E2=80=94 a pre-existing issue not introduced by this macro change. It is fixed by a later patch in this series ("perf session: Add validated swap infrastructure with null-termination checks"), which changes the guard to time_mask. The struct assignment overread (session->time_conv =3D event->time_conv copies sizeof on a potentially shorter event) is separately fixed by "perf session: Use bounded copy for PERF_RECORD_TIME_CONV". Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/lib/perf/include/perf/event.h | 4 +++- tools/perf/util/arm-spe.c | 2 +- tools/perf/util/cs-etm.c | 2 +- tools/perf/util/jitdump.c | 2 +- tools/perf/util/tsc.c | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/lib/perf/include/perf/event.h b/tools/lib/perf/include/p= erf/event.h index 9043dc72b5d68d58..c821143e6e4938c2 100644 --- a/tools/lib/perf/include/perf/event.h +++ b/tools/lib/perf/include/perf/event.h @@ -8,7 +8,9 @@ #include #include /* pid_t */ =20 -#define event_contains(obj, mem) ((obj).header.size > offsetof(typeof(obj)= , mem)) +/* Verify the full field fits within the event, not just its start offset = */ +#define event_contains(obj, mem) \ + ((obj).header.size >=3D offsetof(typeof(obj), mem) + sizeof((obj).mem)) =20 struct perf_record_mmap { struct perf_event_header header; diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c index 2b31da231ef3ec84..6f87e8ef20880425 100644 --- a/tools/perf/util/arm-spe.c +++ b/tools/perf/util/arm-spe.c @@ -1982,7 +1982,7 @@ int arm_spe_process_auxtrace_info(union perf_event *e= vent, spe->tc.time_mult =3D tc->time_mult; spe->tc.time_zero =3D tc->time_zero; =20 - if (event_contains(*tc, time_cycles)) { + if (event_contains(*tc, cap_user_time_short)) { spe->tc.time_cycles =3D tc->time_cycles; spe->tc.time_mask =3D tc->time_mask; spe->tc.cap_user_time_zero =3D tc->cap_user_time_zero; diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 8a639d2e51a4c5bf..02b80389810e767d 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -3496,7 +3496,7 @@ int cs_etm__process_auxtrace_info_full(union perf_eve= nt *event, etm->tc.time_shift =3D tc->time_shift; etm->tc.time_mult =3D tc->time_mult; etm->tc.time_zero =3D tc->time_zero; - if (event_contains(*tc, time_cycles)) { + if (event_contains(*tc, cap_user_time_short)) { etm->tc.time_cycles =3D tc->time_cycles; etm->tc.time_mask =3D tc->time_mask; etm->tc.cap_user_time_zero =3D tc->cap_user_time_zero; diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c index e0ce8b9047298362..e1e160cdec4cf2c2 100644 --- a/tools/perf/util/jitdump.c +++ b/tools/perf/util/jitdump.c @@ -409,7 +409,7 @@ static uint64_t convert_timestamp(struct jit_buf_desc *= jd, uint64_t timestamp) * checks the event size and assigns these extended fields if these * fields are contained in the event. */ - if (event_contains(*time_conv, time_cycles)) { + if (event_contains(*time_conv, cap_user_time_short)) { tc.time_cycles =3D time_conv->time_cycles; tc.time_mask =3D time_conv->time_mask; tc.cap_user_time_zero =3D time_conv->cap_user_time_zero; diff --git a/tools/perf/util/tsc.c b/tools/perf/util/tsc.c index 511a517ce613dff1..ebf289bf6b9d9add 100644 --- a/tools/perf/util/tsc.c +++ b/tools/perf/util/tsc.c @@ -127,7 +127,7 @@ size_t perf_event__fprintf_time_conv(union perf_event *= event, FILE *fp) * when supported cap_user_time_short, for backward compatibility, * prints the extended fields only if they are contained in the event. */ - if (event_contains(*tc, time_cycles)) { + if (event_contains(*tc, cap_user_time_short)) { ret +=3D fprintf(fp, "... Time Cycles %" PRI_lu64 "\n", tc->time_cycles); ret +=3D fprintf(fp, "... Time Mask %#" PRI_lx64 "\n", --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4EBA72727F3; Sun, 10 May 2026 03:34:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384091; cv=none; b=ZrPoW20E0rSE65Vbk7tzHW80am5ztOcWVCVXiB0djioHV5HHKg9BMC5tMrMXqj6Zz+UJ1+H2XgBgyKwTfX66vXyVLbNeDxhdYm9oaPnD3ZJ1HTkErbv+4AvS+0G5JsJOGXDoGfgNuIlkWI5aPk87KUgaKAs+T4KyPEaZzg+LTbI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384091; c=relaxed/simple; bh=drpqOMoHiMEkjNluijjknlVXT5VXSJxN/q8XhxC6Zzk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=i3dmciLJOrId3AzJhD7Xg4W2pK5kePz0oLOkb3+PqYZ4kt/VA15pSrDuEhLjvPpIhRR7Bv+7Vtx1GywqSJIHKxG7qfPey0LwekyAkL/G0tlDc5PwKhXgogZlvK0dKeAFZzS3jzTyzJnV5IhL3YlzZAkw1GofvJ7D2n1EaE8ulpI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QSBhSmgT; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QSBhSmgT" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4F3E1C2BCB8; Sun, 10 May 2026 03:34:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384091; bh=drpqOMoHiMEkjNluijjknlVXT5VXSJxN/q8XhxC6Zzk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QSBhSmgTuhXPQzanHqUdKPtT0jZ3MaPw2+geXl36ASM/yY2G2sxg/gJwsBo4X0l3K K0SL/jO0FHCVmeXBMQnd2IoaGzMBHsrK8qEKHQHEpkQg26Z1InaMioKV3dBd4aTVVL YZUO9g0z48XPXgrEtpj0WMkssILqsC7J6f46+as6uR9rZ2OFilNiPPtfQRZacpWMbe av/531eq4jrRFQSnom3bNGcEicCEbXl2Y1TcHxDRCbvVVAL+INyhketTSKJfj11JKf WwJEwgP8hw9jpi1hmHZuvWjzlhMbQJKsKaGscnHYW7Az+xyUiwPcTjwXjNQ8A3W/6q ua9RyHXm0CP1w== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 03/28] perf zstd: Fix compression error path in zstd_compress_stream_to_records() Date: Sun, 10 May 2026 00:33:54 -0300 Message-ID: <20260510033424.255812-4-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo 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 >=3D src_size =E2=80=94 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() =E2=86=92 record__pushfn() =E2=86=92 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 -=3D 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@kernel.org # Running on a local machine Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- 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 =3D dst; size =3D process_header(record, 0); + /* Output buffer full =E2=80=94 cannot fit even the record header */ + if (size > dst_size) + return -1; compressed +=3D size; dst +=3D size; dst_size -=3D 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 =3D 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 =3D output.pos; size =3D process_header(record, size); --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B6886DDC5; Sun, 10 May 2026 03:34:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384095; cv=none; b=Gq5CXZuIGhxJKPOciFSP3EJw1bVmZ1ZioYkibyCAm4Z7YC0jEhd3LajLNzoTH+JCcWb05f8iud8h/dV0p2zriruiUwh2YnBbma6HwJIL8jT3pM3cxtj7PlugFfoCD/BlQh7B+Uy/6qLDkzxsIhd0lgfaPuOHNipWX+pEhTVkx3Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384095; c=relaxed/simple; bh=d9DVuMkw2hxA1ZIVbwDnE4Xvxc7Ogkae7qqqjfx3sRc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=KJgp+D3FWY9SPq3SyKNgHJZGDWZpYPNKkzEdlU4MIKYNvOi14gebQabvOvUeGE1hK7U6Vh0wKWgz/kmJAOBcu0Lzpbnffou1tU8aJCuIyQvdvLrtFRmMtIekYY9Hf39GviqBx/jIzUS0GPn+vuA+cmjRf2Vb+R0rAL3oOe1M80o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lfV2ayvO; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lfV2ayvO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id AA07FC2BCB8; Sun, 10 May 2026 03:34:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384095; bh=d9DVuMkw2hxA1ZIVbwDnE4Xvxc7Ogkae7qqqjfx3sRc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lfV2ayvOPqleAp61Ghu6YwTZvGKIc1BlJw2iTl2YoP6RLrhb+L66e92XnEt6qtBgJ oIPEya/GYw6TPjY45TrMayrjpJZaZOZC5LxCVpZJnePrVEQ1proYVYslzFWfVabxpY yEkOfOxhXggDzameqyFytjbQ11c33WdPBacOp1RtelXJAISrwK4a+2M5aAfKmxmxUI ckGcr2aaup3xc/lxwCtJ9R8Rw/zpfvBgX5qoPxTGbOYuu91c8jcxrZX5kLPBxLJ1o/ aFVRrxniC3CWQ3OOQpbokSKKqxAinicwfjCM2HARVP6QS9c1UvUUzm+lS8gzaev+yI R/7LhR8h4y7Qw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 04/28] perf zstd: Fix multi-iteration decompression and error handling Date: Sun, 10 May 2026 00:33:55 -0300 Message-ID: <20260510033424.255812-5-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo 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 =3D 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 =E2=80=94 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 Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- 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 =3D input.pos; + size_t prev_out =3D output.pos; + ret =3D 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 =3D dst + output.pos; - output.size =3D dst_size - output.pos; + /* + * Neither stream advanced =E2=80=94 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 =3D=3D prev_in && output.pos =3D=3D prev_out) + return 0; } =20 return output.pos; --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6340F1F0995; Sun, 10 May 2026 03:35:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384101; cv=none; b=hWEJsTcmKdk8YUvhu5XlGK4v3iNmXveAzVr3L2Rlyc+6BqNvyryoX8JHse28/miRf+Rlhoofc6zrMf9VNmj9wCWWS/uvCYcJpPaLiPBEwKaJpNzK0/pmSkrDNNfG63LwX9jG0qPZvuEdM7DMAYootHkqOjsh10UPhovDGfdvYkA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384101; c=relaxed/simple; bh=kO/zKhM1q+2sUbndHha4D8Medz5q2w1EfzTYaMn6Ng4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bmJC7Bs7EH44QWyD4+eHzT6oxhpFN3LQFXR/VJJGMvAXw1PA8RGUKUJontYr/FhIclKX2kNuYciQUphwa/zjDQoGJ1/gSAb1FWdt3VzgRI6LnnmiT19IkllD0QPGtKK7yTxpOAudeOEuysXsY6CWTCghmtT2fR6ZGQdG7PYG1A0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=adBKdL5b; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="adBKdL5b" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CF2EBC2BCF6; Sun, 10 May 2026 03:34:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384101; bh=kO/zKhM1q+2sUbndHha4D8Medz5q2w1EfzTYaMn6Ng4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=adBKdL5b/ZamG8xfLl3FdM3Yhmm6eP4Uz32ZX8HGqOGmb6ixWN/cNp4JICWgf7bs5 ERqE50JV8Z1B36Y63fP76yWN7G9FzfcXzfppuEf3m3BwdQprdvm6fVGVaOWH03ENcG 7TmkyXMNOtUD8wThUWsWX6kJ3/A6nAFYT8C4PKe0mdqo7zOvstvSgPyr8xdNXQXlHw NDGNs61iAB1GkuvdXIiqkkoOipbXI7e/b+nj3fIIGD4gbAnU6Y/JcTkA5CW7jcWiRC NHIb9Y8OI5Pnvf1T9KJ8KbhIoglfrHeZ3059ZXiSb4PusuDgTCCRBFXMY2MLMU6PHT XHIRuroObNf2A== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , "Claude Opus 4.6 (1M context)" Subject: [PATCH 05/28] perf session: Fix PERF_RECORD_READ swap and dump for variable-length events Date: Sun, 10 May 2026 00:33:56 -0300 Message-ID: <20260510033424.255812-6-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo The kernel dynamically sizes PERF_RECORD_READ based on attr.read_format: only the fields enabled by PERF_FORMAT_TOTAL_TIME_ENABLED, PERF_FORMAT_TOTAL_TIME_RUNNING, PERF_FORMAT_ID, and PERF_FORMAT_LOST are emitted, packed with no gaps. perf_event__read_swap() unconditionally byte-swapped time_enabled, time_running, and id at their fixed struct offsets, causing out-of-bounds access on smaller events and swapping the wrong bytes when not all format fields are present. It also dropped the sample_id_all swap entirely. Replace the individual field swaps with a single mem_bswap_64() over the entire tail from value onward. Since every field after pid/tid is u64 regardless of which combination is present, this correctly handles any read_format combination and any trailing sample_id_all fields. Similarly, dump_read() accessed optional fields via fixed struct offsets, displaying values from wrong positions when not all format bits are set. Walk the packed u64 array sequentially instead, with bounds checks against event->header.size. Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 61 +++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index aae0651fb6f025a1..20b70d6fb7cc8ed4 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -354,17 +354,22 @@ static void perf_event__task_swap(union perf_event *e= vent, bool sample_id_all) swap_sample_id_all(event, &event->fork + 1); } =20 -static void perf_event__read_swap(union perf_event *event, bool sample_id_= all) +static void perf_event__read_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { - event->read.pid =3D bswap_32(event->read.pid); - event->read.tid =3D bswap_32(event->read.tid); - event->read.value =3D bswap_64(event->read.value); - event->read.time_enabled =3D bswap_64(event->read.time_enabled); - event->read.time_running =3D bswap_64(event->read.time_running); - event->read.id =3D bswap_64(event->read.id); + size_t tail; =20 - if (sample_id_all) - swap_sample_id_all(event, &event->read + 1); + event->read.pid =3D bswap_32(event->read.pid); + event->read.tid =3D bswap_32(event->read.tid); + /* + * Everything after pid/tid is u64: the read values (variable + * set determined by attr.read_format, which we don't have + * here) optionally followed by sample_id_all fields. + * Since all are u64, swap the entire remaining tail at once. + */ + tail =3D event->header.size - offsetof(struct perf_record_read, value); + tail &=3D ~(size_t)(sizeof(__u64) - 1); + mem_bswap_64(&event->read.value, tail); } =20 static void perf_event__aux_swap(union perf_event *event, bool sample_id_a= ll) @@ -1198,8 +1203,9 @@ static void dump_deferred_callchain(struct evsel *evs= el, union perf_event *event =20 static void dump_read(struct evsel *evsel, union perf_event *event) { - struct perf_record_read *read_event =3D &event->read; u64 read_format; + __u64 *array; + void *end; =20 if (!dump_trace) return; @@ -1211,18 +1217,37 @@ static void dump_read(struct evsel *evsel, union pe= rf_event *event) return; =20 read_format =3D evsel->core.attr.read_format; + /* + * The kernel packs only the enabled read_format fields + * after value, with no gaps. Walk the packed array + * instead of using fixed struct offsets. + */ + array =3D &event->read.value + 1; + end =3D (void *)event + event->header.size; =20 - if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) - printf("... time enabled : %" PRI_lu64 "\n", read_event->time_enabled); + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { + if ((void *)(array + 1) > end) + return; + printf("... time enabled : %" PRI_lu64 "\n", *array++); + } =20 - if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) - printf("... time running : %" PRI_lu64 "\n", read_event->time_running); + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) { + if ((void *)(array + 1) > end) + return; + printf("... time running : %" PRI_lu64 "\n", *array++); + } =20 - if (read_format & PERF_FORMAT_ID) - printf("... id : %" PRI_lu64 "\n", read_event->id); + if (read_format & PERF_FORMAT_ID) { + if ((void *)(array + 1) > end) + return; + printf("... id : %" PRI_lu64 "\n", *array++); + } =20 - if (read_format & PERF_FORMAT_LOST) - printf("... lost : %" PRI_lu64 "\n", read_event->lost); + if (read_format & PERF_FORMAT_LOST) { + if ((void *)(array + 1) > end) + return; + printf("... lost : %" PRI_lu64 "\n", *array++); + } } =20 static struct machine *machines__find_for_cpumode(struct machines *machine= s, --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C833282F25; Sun, 10 May 2026 03:35:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384105; cv=none; b=W3+zbOs9iQiXyCpaR+BSqnAsZKbbETJnR3HlzUYDrlsxqv6svTZ4iam1vQMyOhlkc/dd/vgX/Aj1jxL8zXsfbK6K1MTDc1da6fvhamx5XBHqxX1eEfkvqSQs1Fe/xkSfrTZvRGwDqlYVVm3P3z2tXOEu5YSwCyrZ+heNk+LnQLw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384105; c=relaxed/simple; bh=mDFHP9F/Ds48hmOA7Zbg0QqakHTLZzaOdmIMWu0gr2A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=DgPU9vByw2OjoYlpVv9rJDTLyr8jisVA4evwG2ZHKDXZsOEoGBpbCnR3rMbFNZT6vi3Egg5AnrzliL3KXG3Ig2I+jhmdyyxOeODNOVAC3jK+MD+MJZudPimxuzab+1DJOKovdAEZxTF0BA6Rjy5rEs+Y0z+1ilvKM/ahX+uVstE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=YNHLrTHs; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="YNHLrTHs" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 82F61C2BCB8; Sun, 10 May 2026 03:35:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384105; bh=mDFHP9F/Ds48hmOA7Zbg0QqakHTLZzaOdmIMWu0gr2A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YNHLrTHsr1p93aLUl8qHhJO9cMwPLIGBNmvJt8uGtRTu35mH7Wv+vvJaB1maqeSp4 uypO2SSNJDNXpqwrOdDzksT4SnJ6CLlr+YGmt6UwqZ1tQItYmdWU4lktXfhJkoqaeX thirlPtgd60RNDD003UKhBPprnwYlrw+4r0+xka5PHPkWlJXPbBCEQj03yU26ZAT9E eMyH2rjL3umew5PixpEuZJK4DvBQB6V5cC/7lpCrwRe6YlztxsN6B7XT71FBc6uwN8 bf7Oy3FifLmIhZ+Y6VWM+Qi+vluyzRXFUYUW27rcTTZvUSKKSUYnJQ/lXebuIrR4La cQ6q1hgs/t4Cw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 06/28] perf session: Align auxtrace_info priv size before byte-swapping Date: Sun, 10 May 2026 00:33:57 -0300 Message-ID: <20260510033424.255812-7-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo perf_event__auxtrace_info_swap() passes the raw remainder of header.size to mem_bswap_64(), which swaps in 8-byte chunks. If the size is not a multiple of 8, the last iteration reads and writes 8 bytes from a shorter region, overrunning the event buffer by up to 7 bytes. Round down to a u64 boundary =E2=80=94 priv[] is a u64 array, so any unaligned tail is padding that doesn't need swapping. Also fix swap_sample_id_all() which had the same issue =E2=80=94 replace BUG_ON(size % sizeof(u64)) with rounding down, since crafted events may have unaligned trailing data. Note: the strlen calls in string-field swap handlers (comm, mmap, mmap2, cgroup) are replaced with bounded strnlen by the next patch in this series ("perf session: Add validated swap infrastructure with null-termination checks"). Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 20b70d6fb7cc8ed4..b55c5168ee9f4aae 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -276,10 +276,16 @@ void perf_session__delete(struct perf_session *sessio= n) static void swap_sample_id_all(union perf_event *event, void *data) { void *end =3D (void *) event + event->header.size; - int size =3D end - data; + int size; =20 - BUG_ON(size % sizeof(u64)); - mem_bswap_64(data, size); + if (data >=3D end) + return; + + size =3D end - data; + /* Only swap complete 8-byte elements */ + size &=3D ~(int)(sizeof(u64) - 1); + if (size > 0) + mem_bswap_64(data, size); } =20 static void perf_event__all64_swap(union perf_event *event, @@ -585,6 +591,8 @@ static void perf_event__auxtrace_info_swap(union perf_e= vent *event, =20 size =3D event->header.size; size -=3D (void *)&event->auxtrace_info.priv - (void *)event; + /* priv[] is a u64 array; only swap complete 8-byte elements */ + size &=3D ~(size_t)(sizeof(u64) - 1); mem_bswap_64(event->auxtrace_info.priv, size); } =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5538216132A; Sun, 10 May 2026 03:35:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384111; cv=none; b=JYmgfi87GLsMww52pfaJjMKLVBhPPF+dLzUmJ8rquMgMQ4bebEQ907PXMpOUylp2dOGlI/v9tvD0hGd9SkiuNwW40wP/vx1FryDA/xQAm2CZMX7aiTxZAk+hrK15PehWUeoQY5OgKagde8O7QupItONAAmwvY2b28bquA5DwA1Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384111; c=relaxed/simple; bh=xEZTK/wxQ1l7gCWHtoAm/P0GGgytM79XSYBD93TAk9w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=A7W5gpcClhwREsWxjKuGuRkKQujKPALGFZ4bpI0v4Ti1btkRPT2Ds+JU+ViY5m7CyOPoWiTnPu7tGMbzkkx0sULkBKC0Ke+ixFI7WehHjWlA/dmqikelk073o8zf7SED+x/yVEfPibz0TviMKafpuW5ozUBkrRt6zqqlx7uKO0Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=jFGB96M7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="jFGB96M7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A486FC2BCB8; Sun, 10 May 2026 03:35:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384110; bh=xEZTK/wxQ1l7gCWHtoAm/P0GGgytM79XSYBD93TAk9w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jFGB96M73VMMIG+3YtTWBQxMDs5yxnKddJ1iqS7mPzp4Imxy6GHa/sS7qZAxffuyv 7Jc+/TVFSOfLeyhFABpY/BVYE47v7vWTx1Fl2FlxxghSc1mXKILM7DJagIhRXK87iN HORajZ2vgWQQ0x/aXBDBNywfx+LNQs3+2Ql148hRvhGhgA3GrgVPLj/fNVLhjWfTpp ptYQZZ0IcAaNfu3MySrM1Haxj9aSpZ99YUwJ9z9gOn3/0OP/XLr5UZrO4sgwTlZFpS QD2UgXfjdNo3Wr6hE9f9vyCfEGl6SLr1J57D67U8xKt6Y0/hte6mUzFOUe4jRhpLeM 45ul9I2pCCAJw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 07/28] perf session: Add validated swap infrastructure with null-termination checks Date: Sun, 10 May 2026 00:33:58 -0300 Message-ID: <20260510033424.255812-8-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Change swap callbacks from void to int return so handlers can propagate errors. All 28 existing handlers are converted to return 0 on success, -1 on error. Three new handlers (KSYMBOL, BPF_EVENT, HEADER_FEATURE) are added returning int from the start, with sample_id_all handling for the kernel event types. event_swap() propagates the return to its callers (process_event and peek_event), which skip events that fail to swap. Add perf_event__check_nul() for null-termination enforcement on the common event delivery path for MMAP, MMAP2, COMM, CGROUP, and KSYMBOL events. Events with unterminated strings are skipped =E2=80=94 native-endian files are mapped read-only, so writing a NUL byte in place would segfault. Swap handler hardening: - Use strnlen bounded by event size (instead of strlen) in COMM/MMAP/MMAP2/CGROUP swap handlers, returning -1 on unterminated strings. - Bounds check text_poke old_len+new_len before computing the sample_id offset, returning -1 on overflow. Use offsetof() for the native-path check in machines__deliver_event() since sizeof() includes struct padding past the flexible array. - Fix PERF_RECORD_SWITCH sample_id_all: non-CPU_WIDE SWITCH events have sample_id immediately after the 8-byte header, not at sizeof(struct perf_record_switch) which is the CPU_WIDE variant size. - Fix perf_event__time_conv_swap() guard: check time_mask (the last field accessed) instead of time_cycles, so a short event that fits time_cycles but not time_mask does not cause an out-of-bounds bswap_64. - Handle ABI0 (attr.size =3D=3D 0) in perf_event__attr_swap() by substituting PERF_ATTR_SIZE_VER0, so bswap_safe() correctly swaps VER0 fields instead of skipping everything. - peek_events: initialize event pointer to NULL to avoid dereferencing stack garbage on early peek_event() failure; on swap failure, advance past the malformed entry instead of aborting the loop. Note: the nr-field bounds checks for namespaces, thread_map, cpu_map, and stat_config arrays are added by a subsequent patch ("perf session: Validate nr fields against event size on both swap and common paths"). The HEADER_ATTR attr.size validation is added by ("perf session: Validate HEADER_ATTR alignment and attr.size before swapping"). By establishing the int-returning swap infrastructure first, all subsequent hardening patches can use direct error returns from day one =E2=80=94 no poison values, no workarounds for void return. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 411 ++++++++++++++++++++++++++++++-------- 1 file changed, 323 insertions(+), 88 deletions(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index b55c5168ee9f4aae..18e60ccf6829f05a 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -288,28 +288,43 @@ static void swap_sample_id_all(union perf_event *even= t, void *data) mem_bswap_64(data, size); } =20 -static void perf_event__all64_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__all64_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { struct perf_event_header *hdr =3D &event->header; - mem_bswap_64(hdr + 1, event->header.size - sizeof(*hdr)); + size_t size =3D event->header.size - sizeof(*hdr); + + /* Round down: mem_bswap_64() would overrun on unaligned tail */ + size &=3D ~(size_t)(sizeof(u64) - 1); + mem_bswap_64(hdr + 1, size); + return 0; } =20 -static void perf_event__comm_swap(union perf_event *event, bool sample_id_= all) +static int perf_event__comm_swap(union perf_event *event, bool sample_id_a= ll) { event->comm.pid =3D bswap_32(event->comm.pid); event->comm.tid =3D bswap_32(event->comm.tid); =20 if (sample_id_all) { void *data =3D &event->comm.comm; + void *end =3D (void *)event + event->header.size; + size_t len =3D strnlen(data, end - data); =20 - data +=3D PERF_ALIGN(strlen(data) + 1, sizeof(u64)); + /* + * No NUL within the event boundary =E2=80=94 can't locate where + * sample_id_all starts. Reject so the event is skipped + * rather than swapping garbage. + */ + if (len =3D=3D (size_t)(end - data)) + return -1; + data +=3D PERF_ALIGN(len + 1, sizeof(u64)); swap_sample_id_all(event, data); } + return 0; } =20 -static void perf_event__mmap_swap(union perf_event *event, - bool sample_id_all) +static int perf_event__mmap_swap(union perf_event *event, + bool sample_id_all) { event->mmap.pid =3D bswap_32(event->mmap.pid); event->mmap.tid =3D bswap_32(event->mmap.tid); @@ -319,13 +334,19 @@ static void perf_event__mmap_swap(union perf_event *e= vent, =20 if (sample_id_all) { void *data =3D &event->mmap.filename; + void *end =3D (void *)event + event->header.size; + size_t len =3D strnlen(data, end - data); =20 - data +=3D PERF_ALIGN(strlen(data) + 1, sizeof(u64)); + /* See comment in perf_event__comm_swap() */ + if (len =3D=3D (size_t)(end - data)) + return -1; + data +=3D PERF_ALIGN(len + 1, sizeof(u64)); swap_sample_id_all(event, data); } + return 0; } =20 -static void perf_event__mmap2_swap(union perf_event *event, +static int perf_event__mmap2_swap(union perf_event *event, bool sample_id_all) { event->mmap2.pid =3D bswap_32(event->mmap2.pid); @@ -343,12 +364,19 @@ static void perf_event__mmap2_swap(union perf_event *= event, =20 if (sample_id_all) { void *data =3D &event->mmap2.filename; + void *end =3D (void *)event + event->header.size; + size_t len =3D strnlen(data, end - data); =20 - data +=3D PERF_ALIGN(strlen(data) + 1, sizeof(u64)); + /* See comment in perf_event__comm_swap() */ + if (len =3D=3D (size_t)(end - data)) + return -1; + data +=3D PERF_ALIGN(len + 1, sizeof(u64)); swap_sample_id_all(event, data); } + return 0; } -static void perf_event__task_swap(union perf_event *event, bool sample_id_= all) + +static int perf_event__task_swap(union perf_event *event, bool sample_id_a= ll) { event->fork.pid =3D bswap_32(event->fork.pid); event->fork.tid =3D bswap_32(event->fork.tid); @@ -358,10 +386,11 @@ static void perf_event__task_swap(union perf_event *e= vent, bool sample_id_all) =20 if (sample_id_all) swap_sample_id_all(event, &event->fork + 1); + return 0; } =20 -static void perf_event__read_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__read_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { size_t tail; =20 @@ -376,9 +405,10 @@ static void perf_event__read_swap(union perf_event *ev= ent, tail =3D event->header.size - offsetof(struct perf_record_read, value); tail &=3D ~(size_t)(sizeof(__u64) - 1); mem_bswap_64(&event->read.value, tail); + return 0; } =20 -static void perf_event__aux_swap(union perf_event *event, bool sample_id_a= ll) +static int perf_event__aux_swap(union perf_event *event, bool sample_id_al= l) { event->aux.aux_offset =3D bswap_64(event->aux.aux_offset); event->aux.aux_size =3D bswap_64(event->aux.aux_size); @@ -386,19 +416,21 @@ static void perf_event__aux_swap(union perf_event *ev= ent, bool sample_id_all) =20 if (sample_id_all) swap_sample_id_all(event, &event->aux + 1); + return 0; } =20 -static void perf_event__itrace_start_swap(union perf_event *event, - bool sample_id_all) +static int perf_event__itrace_start_swap(union perf_event *event, + bool sample_id_all) { event->itrace_start.pid =3D bswap_32(event->itrace_start.pid); event->itrace_start.tid =3D bswap_32(event->itrace_start.tid); =20 if (sample_id_all) swap_sample_id_all(event, &event->itrace_start + 1); + return 0; } =20 -static void perf_event__switch_swap(union perf_event *event, bool sample_i= d_all) +static int perf_event__switch_swap(union perf_event *event, bool sample_id= _all) { if (event->header.type =3D=3D PERF_RECORD_SWITCH_CPU_WIDE) { event->context_switch.next_prev_pid =3D @@ -407,30 +439,45 @@ static void perf_event__switch_swap(union perf_event = *event, bool sample_id_all) bswap_32(event->context_switch.next_prev_tid); } =20 - if (sample_id_all) - swap_sample_id_all(event, &event->context_switch + 1); + if (sample_id_all) { + /* + * PERF_RECORD_SWITCH has no fields beyond the header; + * SWITCH_CPU_WIDE adds pid/tid. Use the right offset + * so sample_id starts at the correct position. + */ + if (event->header.type =3D=3D PERF_RECORD_SWITCH) + swap_sample_id_all(event, (void *)event + sizeof(event->header)); + else + swap_sample_id_all(event, &event->context_switch + 1); + } + return 0; } =20 -static void perf_event__text_poke_swap(union perf_event *event, bool sampl= e_id_all) +static int perf_event__text_poke_swap(union perf_event *event, bool sample= _id_all) { event->text_poke.addr =3D bswap_64(event->text_poke.addr); event->text_poke.old_len =3D bswap_16(event->text_poke.old_len); event->text_poke.new_len =3D bswap_16(event->text_poke.new_len); =20 if (sample_id_all) { + void *data =3D &event->text_poke.old_len; + void *end =3D (void *)event + event->header.size; size_t len =3D sizeof(event->text_poke.old_len) + sizeof(event->text_poke.new_len) + event->text_poke.old_len + event->text_poke.new_len; - void *data =3D &event->text_poke.old_len; =20 + /* old_len + new_len exceeds event =E2=80=94 can't find sample_id_all */ + if (data + len > end) + return -1; data +=3D PERF_ALIGN(len, sizeof(u64)); swap_sample_id_all(event, data); } + return 0; } =20 -static void perf_event__throttle_swap(union perf_event *event, - bool sample_id_all) +static int perf_event__throttle_swap(union perf_event *event, + bool sample_id_all) { event->throttle.time =3D bswap_64(event->throttle.time); event->throttle.id =3D bswap_64(event->throttle.id); @@ -438,10 +485,11 @@ static void perf_event__throttle_swap(union perf_even= t *event, =20 if (sample_id_all) swap_sample_id_all(event, &event->throttle + 1); + return 0; } =20 -static void perf_event__namespaces_swap(union perf_event *event, - bool sample_id_all) +static int perf_event__namespaces_swap(union perf_event *event, + bool sample_id_all) { u64 i; =20 @@ -458,18 +506,25 @@ static void perf_event__namespaces_swap(union perf_ev= ent *event, =20 if (sample_id_all) swap_sample_id_all(event, &event->namespaces.link_info[i]); + return 0; } =20 -static void perf_event__cgroup_swap(union perf_event *event, bool sample_i= d_all) +static int perf_event__cgroup_swap(union perf_event *event, bool sample_id= _all) { event->cgroup.id =3D bswap_64(event->cgroup.id); =20 if (sample_id_all) { void *data =3D &event->cgroup.path; + void *end =3D (void *)event + event->header.size; + size_t len =3D strnlen(data, end - data); =20 - data +=3D PERF_ALIGN(strlen(data) + 1, sizeof(u64)); + /* See comment in perf_event__comm_swap() */ + if (len =3D=3D (size_t)(end - data)) + return -1; + data +=3D PERF_ALIGN(len + 1, sizeof(u64)); swap_sample_id_all(event, data); } + return 0; } =20 static u8 revbyte(u8 b) @@ -510,9 +565,19 @@ void perf_event__attr_swap(struct perf_event_attr *att= r) attr->type =3D bswap_32(attr->type); attr->size =3D bswap_32(attr->size); =20 -#define bswap_safe(f, n) \ - (attr->size > (offsetof(struct perf_event_attr, f) + \ - sizeof(attr->f) * (n))) + /* + * ABI0: size =3D=3D 0 means the producer didn't set it. + * Assume PERF_ATTR_SIZE_VER0 so bswap_safe() below + * correctly swaps the VER0 fields instead of skipping + * everything. Same convention as read_attr(). + */ + if (!attr->size) + attr->size =3D PERF_ATTR_SIZE_VER0; + +/* Verify the full field extent fits, not just its start offset */ +#define bswap_safe(f, n) \ + (attr->size >=3D (offsetof(struct perf_event_attr, f) + \ + sizeof(attr->f) * ((n) + 1))) #define bswap_field(f, sz) \ do { \ if (bswap_safe(f, 0)) \ @@ -550,8 +615,8 @@ do { \ #undef bswap_safe } =20 -static void perf_event__hdr_attr_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__hdr_attr_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { size_t size; =20 @@ -560,30 +625,34 @@ static void perf_event__hdr_attr_swap(union perf_even= t *event, size =3D event->header.size; size -=3D perf_record_header_attr_id(event) - (void *)event; mem_bswap_64(perf_record_header_attr_id(event), size); + return 0; } =20 -static void perf_event__event_update_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__event_update_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->event_update.type =3D bswap_64(event->event_update.type); event->event_update.id =3D bswap_64(event->event_update.id); + return 0; } =20 -static void perf_event__event_type_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__event_type_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->event_type.event_type.event_id =3D bswap_64(event->event_type.event_type.event_id); + return 0; } =20 -static void perf_event__tracing_data_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__tracing_data_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->tracing_data.size =3D bswap_32(event->tracing_data.size); + return 0; } =20 -static void perf_event__auxtrace_info_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__auxtrace_info_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { size_t size; =20 @@ -594,10 +663,11 @@ static void perf_event__auxtrace_info_swap(union perf= _event *event, /* priv[] is a u64 array; only swap complete 8-byte elements */ size &=3D ~(size_t)(sizeof(u64) - 1); mem_bswap_64(event->auxtrace_info.priv, size); + return 0; } =20 -static void perf_event__auxtrace_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__auxtrace_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->auxtrace.size =3D bswap_64(event->auxtrace.size); event->auxtrace.offset =3D bswap_64(event->auxtrace.offset); @@ -605,10 +675,11 @@ static void perf_event__auxtrace_swap(union perf_even= t *event, event->auxtrace.idx =3D bswap_32(event->auxtrace.idx); event->auxtrace.tid =3D bswap_32(event->auxtrace.tid); event->auxtrace.cpu =3D bswap_32(event->auxtrace.cpu); + return 0; } =20 -static void perf_event__auxtrace_error_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__auxtrace_error_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->auxtrace_error.type =3D bswap_32(event->auxtrace_error.type); event->auxtrace_error.code =3D bswap_32(event->auxtrace_error.code); @@ -623,10 +694,11 @@ static void perf_event__auxtrace_error_swap(union per= f_event *event, event->auxtrace_error.machine_pid =3D bswap_32(event->auxtrace_error.mac= hine_pid); event->auxtrace_error.vcpu =3D bswap_32(event->auxtrace_error.vcpu); } + return 0; } =20 -static void perf_event__thread_map_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__thread_map_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { unsigned i; =20 @@ -634,10 +706,11 @@ static void perf_event__thread_map_swap(union perf_ev= ent *event, =20 for (i =3D 0; i < event->thread_map.nr; i++) event->thread_map.entries[i].pid =3D bswap_64(event->thread_map.entries[= i].pid); + return 0; } =20 -static void perf_event__cpu_map_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__cpu_map_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { struct perf_record_cpu_map_data *data =3D &event->cpu_map.data; =20 @@ -675,20 +748,22 @@ static void perf_event__cpu_map_swap(union perf_event= *event, default: break; } + return 0; } =20 -static void perf_event__stat_config_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__stat_config_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { u64 size; =20 size =3D bswap_64(event->stat_config.nr) * sizeof(event->stat_config.dat= a[0]); size +=3D 1; /* nr item itself */ mem_bswap_64(&event->stat_config.nr, size); + return 0; } =20 -static void perf_event__stat_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__stat_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->stat.id =3D bswap_64(event->stat.id); event->stat.thread =3D bswap_32(event->stat.thread); @@ -696,44 +771,90 @@ static void perf_event__stat_swap(union perf_event *e= vent, event->stat.val =3D bswap_64(event->stat.val); event->stat.ena =3D bswap_64(event->stat.ena); event->stat.run =3D bswap_64(event->stat.run); + return 0; } =20 -static void perf_event__stat_round_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__stat_round_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->stat_round.type =3D bswap_64(event->stat_round.type); event->stat_round.time =3D bswap_64(event->stat_round.time); + return 0; } =20 -static void perf_event__time_conv_swap(union perf_event *event, - bool sample_id_all __maybe_unused) +static int perf_event__time_conv_swap(union perf_event *event, + bool sample_id_all __maybe_unused) { event->time_conv.time_shift =3D bswap_64(event->time_conv.time_shift); event->time_conv.time_mult =3D bswap_64(event->time_conv.time_mult); event->time_conv.time_zero =3D bswap_64(event->time_conv.time_zero); =20 - if (event_contains(event->time_conv, time_cycles)) { + if (event_contains(event->time_conv, time_mask)) { event->time_conv.time_cycles =3D bswap_64(event->time_conv.time_cycles); event->time_conv.time_mask =3D bswap_64(event->time_conv.time_mask); } + return 0; } =20 -static void +static int perf_event__schedstat_cpu_swap(union perf_event *event __maybe_unused, bool sample_id_all __maybe_unused) { /* FIXME */ + return 0; } =20 -static void +static int perf_event__schedstat_domain_swap(union perf_event *event __maybe_unused, bool sample_id_all __maybe_unused) { /* FIXME */ + return 0; +} + +static int perf_event__ksymbol_swap(union perf_event *event, + bool sample_id_all) +{ + event->ksymbol.addr =3D bswap_64(event->ksymbol.addr); + event->ksymbol.len =3D bswap_32(event->ksymbol.len); + event->ksymbol.ksym_type =3D bswap_16(event->ksymbol.ksym_type); + event->ksymbol.flags =3D bswap_16(event->ksymbol.flags); + + if (sample_id_all) { + void *data =3D &event->ksymbol.name; + void *end =3D (void *)event + event->header.size; + size_t len =3D strnlen(data, end - data); + + /* See comment in perf_event__comm_swap() */ + if (len =3D=3D (size_t)(end - data)) + return -1; + data +=3D PERF_ALIGN(len + 1, sizeof(u64)); + swap_sample_id_all(event, data); + } + return 0; } =20 -typedef void (*perf_event__swap_op)(union perf_event *event, - bool sample_id_all); +static int perf_event__bpf_event_swap(union perf_event *event, + bool sample_id_all) +{ + event->bpf.type =3D bswap_16(event->bpf.type); + event->bpf.flags =3D bswap_16(event->bpf.flags); + event->bpf.id =3D bswap_32(event->bpf.id); + + if (sample_id_all) + swap_sample_id_all(event, &event->bpf + 1); + return 0; +} + +static int perf_event__header_feature_swap(union perf_event *event, + bool sample_id_all __maybe_unused) +{ + event->feat.feat_id =3D bswap_64(event->feat.feat_id); + return 0; +} + +typedef int (*perf_event__swap_op)(union perf_event *event, + bool sample_id_all); =20 static perf_event__swap_op perf_event__swap_ops[] =3D { [PERF_RECORD_MMAP] =3D perf_event__mmap_swap, @@ -753,6 +874,8 @@ static perf_event__swap_op perf_event__swap_ops[] =3D { [PERF_RECORD_SWITCH_CPU_WIDE] =3D perf_event__switch_swap, [PERF_RECORD_NAMESPACES] =3D perf_event__namespaces_swap, [PERF_RECORD_CGROUP] =3D perf_event__cgroup_swap, + [PERF_RECORD_KSYMBOL] =3D perf_event__ksymbol_swap, + [PERF_RECORD_BPF_EVENT] =3D perf_event__bpf_event_swap, [PERF_RECORD_TEXT_POKE] =3D perf_event__text_poke_swap, [PERF_RECORD_AUX_OUTPUT_HW_ID] =3D perf_event__all64_swap, [PERF_RECORD_CALLCHAIN_DEFERRED] =3D perf_event__all64_swap, @@ -760,6 +883,7 @@ static perf_event__swap_op perf_event__swap_ops[] =3D { [PERF_RECORD_HEADER_EVENT_TYPE] =3D perf_event__event_type_swap, [PERF_RECORD_HEADER_TRACING_DATA] =3D perf_event__tracing_data_swap, [PERF_RECORD_HEADER_BUILD_ID] =3D NULL, + [PERF_RECORD_HEADER_FEATURE] =3D perf_event__header_feature_swap, [PERF_RECORD_ID_INDEX] =3D perf_event__all64_swap, [PERF_RECORD_AUXTRACE_INFO] =3D perf_event__auxtrace_info_swap, [PERF_RECORD_AUXTRACE] =3D perf_event__auxtrace_swap, @@ -1484,6 +1608,25 @@ static int session__flush_deferred_samples(struct pe= rf_session *session, return ret; } =20 +/* + * Return true if the string field is properly null-terminated + * within the event boundary. Native-endian files are mapped + * read-only (MAP_SHARED + PROT_READ) so we cannot write a + * null byte in place; skip the event instead. + */ +static bool perf_event__check_nul(const char *str, const void *end, const = char *event_name) +{ + size_t max_len =3D (const char *)end - str; + + if (max_len =3D=3D 0 || strnlen(str, max_len) =3D=3D max_len) { + pr_warning("WARNING: PERF_RECORD_%s: string not null-terminated, skippin= g event\n", + event_name); + return false; + } + + return true; +} + static int machines__deliver_event(struct machines *machines, struct evlist *evlist, union perf_event *event, @@ -1534,16 +1677,32 @@ static int machines__deliver_event(struct machines = *machines, } return evlist__deliver_sample(evlist, tool, event, sample, evsel, machin= e); case PERF_RECORD_MMAP: + if (!perf_event__check_nul(event->mmap.filename, + (void *)event + event->header.size, + "MMAP")) + return 0; return tool->mmap(tool, event, sample, machine); case PERF_RECORD_MMAP2: if (event->header.misc & PERF_RECORD_MISC_PROC_MAP_PARSE_TIMEOUT) ++evlist->stats.nr_proc_map_timeout; + if (!perf_event__check_nul(event->mmap2.filename, + (void *)event + event->header.size, + "MMAP2")) + return 0; return tool->mmap2(tool, event, sample, machine); case PERF_RECORD_COMM: + if (!perf_event__check_nul(event->comm.comm, + (void *)event + event->header.size, + "COMM")) + return 0; return tool->comm(tool, event, sample, machine); case PERF_RECORD_NAMESPACES: return tool->namespaces(tool, event, sample, machine); case PERF_RECORD_CGROUP: + if (!perf_event__check_nul(event->cgroup.path, + (void *)event + event->header.size, + "CGROUP")) + return 0; return tool->cgroup(tool, event, sample, machine); case PERF_RECORD_FORK: return tool->fork(tool, event, sample, machine); @@ -1582,11 +1741,25 @@ static int machines__deliver_event(struct machines = *machines, case PERF_RECORD_SWITCH_CPU_WIDE: return tool->context_switch(tool, event, sample, machine); case PERF_RECORD_KSYMBOL: + if (!perf_event__check_nul(event->ksymbol.name, + (void *)event + event->header.size, + "KSYMBOL")) + return 0; return tool->ksymbol(tool, event, sample, machine); case PERF_RECORD_BPF_EVENT: return tool->bpf(tool, event, sample, machine); - case PERF_RECORD_TEXT_POKE: + case PERF_RECORD_TEXT_POKE: { + /* offsetof(bytes), not sizeof =E2=80=94 sizeof includes padding past th= e flexible array */ + size_t text_poke_len =3D offsetof(struct perf_record_text_poke_event, by= tes) + + event->text_poke.old_len + + event->text_poke.new_len; + + if (event->header.size < text_poke_len) { + pr_warning("WARNING: PERF_RECORD_TEXT_POKE: old_len+new_len exceeds eve= nt, skipping\n"); + return 0; + } return tool->text_poke(tool, event, sample, machine); + } case PERF_RECORD_AUX_OUTPUT_HW_ID: return tool->aux_output_hw_id(tool, event, sample, machine); case PERF_RECORD_CALLCHAIN_DEFERRED: @@ -1792,19 +1965,40 @@ int perf_session__deliver_synth_attr_event(struct p= erf_session *session, return perf_session__deliver_synth_event(session, &ev.ev, NULL); } =20 +static int event_swap(union perf_event *event, bool sample_id_all) +{ + perf_event__swap_op swap; + + /* Prevent OOB read on perf_event__swap_ops[] from crafted type */ + if (event->header.type >=3D PERF_RECORD_HEADER_MAX) + return 0; + + swap =3D perf_event__swap_ops[event->header.type]; + if (swap) + return swap(event, sample_id_all); + return 0; +} + /* * Minimum event sizes indexed by type. Checked before swap and * processing so that both cross-endian and native-endian paths * are protected from accessing fields past the event boundary. * Zero means no minimum beyond the 8-byte header (already * enforced by the reader). + * + * These values represent the smallest event the kernel has ever + * emitted for each type, so they do not reject legitimate legacy + * perf.data files from older kernels. Variable-length events + * use offsetof() to the first variable field; the variable + * content is validated separately (e.g., perf_event__check_nul). */ static const u32 perf_event__min_size[PERF_RECORD_HEADER_MAX] =3D { /* * offsetof() for types with a trailing variable-length string * (filename, comm, path, name, msg): sizeof() includes a * PATH_MAX or fixed-size array, but valid events only need - * the fixed fields. Null-termination is checked separately. + * the fixed fields. Null-termination is checked separately + * by perf_event__check_nul(). * * PERF_RECORD_SAMPLE is omitted: all64_swap is bounded by * header.size, and the internal layout varies by sample_type @@ -1812,6 +2006,7 @@ static const u32 perf_event__min_size[PERF_RECORD_HEA= DER_MAX] =3D { */ [PERF_RECORD_MMAP] =3D offsetof(struct perf_record_mmap, filename), [PERF_RECORD_LOST] =3D sizeof(struct perf_record_lost), + /* comm[] is variable-length; kernel aligns to 8 bytes */ [PERF_RECORD_COMM] =3D offsetof(struct perf_record_comm, comm), [PERF_RECORD_EXIT] =3D sizeof(struct perf_record_fork), [PERF_RECORD_THROTTLE] =3D sizeof(struct perf_record_throttle), @@ -1819,7 +2014,9 @@ static const u32 perf_event__min_size[PERF_RECORD_HEA= DER_MAX] =3D { [PERF_RECORD_FORK] =3D sizeof(struct perf_record_fork), /* * The kernel dynamically sizes PERF_RECORD_READ based on - * attr.read_format =E2=80=94 the minimum has just pid + tid + value. + * attr.read_format =E2=80=94 only the enabled fields are emitted, + * packed with no gaps. The minimum valid event has just + * pid + tid + one u64 value (no optional fields). */ [PERF_RECORD_READ] =3D offsetof(struct perf_record_read, time_enabled), [PERF_RECORD_MMAP2] =3D offsetof(struct perf_record_mmap2, filename), @@ -1841,14 +2038,25 @@ static const u32 perf_event__min_size[PERF_RECORD_H= EADER_MAX] =3D { [PERF_RECORD_AUXTRACE] =3D sizeof(struct perf_record_auxtrace), [PERF_RECORD_AUXTRACE_ERROR] =3D offsetof(struct perf_record_auxtrace_e= rror, msg), [PERF_RECORD_THREAD_MAP] =3D sizeof(struct perf_record_thread_map), - /* Smallest valid variant is RANGE_CPUS: header(8) + type(2) + range(6) */ + /* + * sizeof(perf_record_cpu_map) is 20 because the outer struct + * isn't packed and GCC adds 2 bytes of trailing padding. + * The smallest valid variant (RANGE_CPUS) is only 16 bytes: + * header(8) + type(2) + range_cpu_data(6). Per-variant + * bounds are checked in the swap handler via payload. + */ [PERF_RECORD_CPU_MAP] =3D sizeof(struct perf_event_header) + sizeof(__u16) + sizeof(struct perf_record_range_cpu_map), [PERF_RECORD_STAT_CONFIG] =3D sizeof(struct perf_record_stat_config), [PERF_RECORD_STAT] =3D sizeof(struct perf_record_stat), [PERF_RECORD_STAT_ROUND] =3D sizeof(struct perf_record_stat_round), - /* Union inflates sizeof; use fixed header fields as minimum */ + /* + * EVENT_UPDATE has a union whose largest member (cpus) + * inflates sizeof to 40, but SCALE events are only 32 + * and UNIT/NAME events can be even smaller. Use the + * fixed header fields (header + type + id) as minimum. + */ [PERF_RECORD_EVENT_UPDATE] =3D offsetof(struct perf_record_event_update= , scale), [PERF_RECORD_TIME_CONV] =3D offsetof(struct perf_record_time_conv, tim= e_cycles), [PERF_RECORD_ID_INDEX] =3D sizeof(struct perf_record_id_index), @@ -1866,19 +2074,6 @@ static const u32 perf_event__min_size[PERF_RECORD_HE= ADER_MAX] =3D { [PERF_RECORD_SCHEDSTAT_DOMAIN] =3D offsetof(struct perf_record_schedsta= t_domain, v15), }; =20 -static void event_swap(union perf_event *event, bool sample_id_all) -{ - perf_event__swap_op swap; - - /* Prevent OOB read on perf_event__swap_ops[] from crafted type */ - if (event->header.type >=3D PERF_RECORD_HEADER_MAX) - return; - - swap =3D perf_event__swap_ops[event->header.type]; - if (swap) - swap(event, sample_id_all); -} - int perf_session__peek_event(struct perf_session *session, off_t file_offs= et, void *buf, size_t buf_sz, union perf_event **event_ptr, @@ -1896,7 +2091,6 @@ int perf_session__peek_event(struct perf_session *ses= sion, off_t file_offset, if (event->header.size < sizeof(struct perf_event_header)) return -1; =20 - /* Reject undersized events on the native-endian fast path */ if (event->header.type < PERF_RECORD_HEADER_MAX) { u32 min_sz =3D perf_event__min_size[event->header.type]; =20 @@ -1935,7 +2129,6 @@ int perf_session__peek_event(struct perf_session *ses= sion, off_t file_offset, if (readn(fd, buf, rest) !=3D (ssize_t)rest) return -1; =20 - /* Reject undersized events before swapping */ if (event->header.type < PERF_RECORD_HEADER_MAX) { u32 min_sz =3D perf_event__min_size[event->header.type]; =20 @@ -1949,8 +2142,16 @@ int perf_session__peek_event(struct perf_session *se= ssion, off_t file_offset, } } =20 - if (session->header.needs_swap) - event_swap(event, evlist__sample_id_all(session->evlist)); + if (session->header.needs_swap && + event_swap(event, evlist__sample_id_all(session->evlist))) { + /* + * The header was already swapped so header.size is + * valid =E2=80=94 expose the event so callers can advance + * past this malformed entry instead of aborting. + */ + *event_ptr =3D event; + return -1; + } =20 out_parse_sample: =20 @@ -1968,15 +2169,34 @@ int perf_session__peek_events(struct perf_session *= session, u64 offset, { u64 max_offset =3D offset + size; char buf[PERF_SAMPLE_MAX_SIZE]; - union perf_event *event; + /* + * Initialized to NULL so the first-iteration error path + * doesn't dereference stack garbage. On subsequent failures + * event may point into buf from a prior read =E2=80=94 peek_event + * sets *event_ptr on min_sz and swap failures so the header + * is from the current (failed) event, not a stale one. + */ + union perf_event *event =3D NULL; int err; =20 do { + event =3D NULL; err =3D perf_session__peek_event(session, offset, buf, PERF_SAMPLE_MAX_SIZE, &event, NULL); - if (err) - return err; + if (err) { + /* + * peek_event sets event_ptr when it read enough + * to know the event size (min_sz and swap failures). + * If event is NULL or size is 0, we can't advance + * and must abort. Otherwise skip past this entry. + */ + if (event && event->header.size) + offset +=3D event->header.size; + else + return err; + continue; + } =20 err =3D cb(session, event, offset, data); if (err) @@ -2014,8 +2234,12 @@ static s64 perf_session__process_event(struct perf_s= ession *session, } } =20 - if (session->header.needs_swap) - event_swap(event, evlist__sample_id_all(evlist)); + if (session->header.needs_swap && + event_swap(event, evlist__sample_id_all(evlist))) { + pr_warning("WARNING: swap failed for %s event, skipping\n", + perf_event__name(event->header.type)); + return 0; + } =20 if (event->header.type >=3D PERF_RECORD_HEADER_MAX) { /* perf should not support unaligned event, stop here. */ @@ -2496,6 +2720,17 @@ reader__mmap(struct reader *rd, struct perf_session = *session) char *buf, **mmaps =3D rd->mmaps; u64 page_offset; =20 + /* + * Native-endian: MAP_SHARED + PROT_READ =E2=80=94 the kernel + * guarantees page-level coherence but a concurrent writer + * could modify the file between validation and use. This + * is a theoretical TOCTOU that affects the entire perf.data + * processing pipeline; fixing it would require copying each + * event to a private buffer before processing. + * + * Cross-endian: MAP_PRIVATE + PROT_WRITE =E2=80=94 swap handlers + * get a copy-on-write snapshot immune to concurrent writes. + */ mmap_prot =3D PROT_READ; mmap_flags =3D MAP_SHARED; =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9DAC824677F; Sun, 10 May 2026 03:35:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384116; cv=none; b=mF3AqqK40U2rFe9NCzPWnTUXpXKekW/wOZhgmCR0mi5BUpuh3CzH9HzLI+Bqi996YpXI99YXF/D2ADN5JPgiDP/e8pbvOhHnuBP9u/l8TrKCWK8ktGaGb85LfMmG5B4tLngqIvcdCbNeuvVU5aC3U9ntpGEeUcBUc2PyL4+JNYA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384116; c=relaxed/simple; bh=VtIhWKBfyBo96hch+CwO3Q/k0Fh9v33JHcaLu/UJ9ww=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tt4wmXEUL9G9tSnQQet0MXcoNsQasZ3aXOlQO+Qil69+kwuYXOUB8crhjl3je5aML+DhX1DBcM67w766ystKqUROOlL3y0HBySWVFcpbcjP3JzrcbAQNmNqKxb9QXfBu/z0dW9H5KlO3FsnVTOjgVQEr6rd1Vbfx4qslA9IL858= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=q0xNIi+H; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="q0xNIi+H" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3E857C2BCF6; Sun, 10 May 2026 03:35:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384116; bh=VtIhWKBfyBo96hch+CwO3Q/k0Fh9v33JHcaLu/UJ9ww=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=q0xNIi+HN+Ql3JHYZAnGNy8BH0lwZRP/RMAIyzsimaIrxWVb3UXEpqXsvLsBLiCei 6UvShERI4oJa1vaIIiYXXQFJlAhvLPbqwEfq28oJoSpGekWchUCGoV6SU53aXuL+Og EHdOXFULqROEeUyvd2cLmql9hVHIGMfslG7N2dM0BazOyzKoNJVvzu2u7usDES+uAX VDc/J28km5zbiPyyORXBjeTgp7H1FktrNEGig4/NsCXSQQSgqvoqwOLsNMqGPobR2S OYoftcqHnwGe4XCFMDqLQo8E9lJZa7nN/FXDhfKKsWD2z+zMVYAHGaGtXxg6OYzcLZ bVHUOO48qKJ8A== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 08/28] perf session: Use bounded copy for PERF_RECORD_TIME_CONV Date: Sun, 10 May 2026 00:33:59 -0300 Message-ID: <20260510033424.255812-9-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo session->time_conv =3D event->time_conv copies sizeof(struct perf_record_time_conv) bytes unconditionally, but older kernels emit shorter TIME_CONV events without the time_cycles, time_mask, cap_user_time_zero, and cap_user_time_short fields. For a 32-byte event (the original format), this reads 24 bytes past the event boundary into adjacent mmap'd data. The garbage values end up in session->time_conv and can cause incorrect TSC conversion if cap_user_time_zero happens to be non-zero. Replace the struct assignment with a bounded memcpy capped at event->header.size, zeroing the remainder so extended fields default to off when absent. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 18e60ccf6829f05a..776061afd568858a 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1894,7 +1894,14 @@ static s64 perf_session__process_user_event(struct p= erf_session *session, err =3D tool->stat_round(tool, session, event); break; case PERF_RECORD_TIME_CONV: - session->time_conv =3D event->time_conv; + /* + * Bounded copy: older kernels emit a shorter struct + * without time_cycles/time_mask/cap_user_time_*. + * Zero the rest so extended fields default to off. + */ + memset(&session->time_conv, 0, sizeof(session->time_conv)); + memcpy(&session->time_conv, &event->time_conv, + min((size_t)event->header.size, sizeof(session->time_conv))); err =3D tool->time_conv(tool, session, event); break; case PERF_RECORD_HEADER_FEATURE: --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 22D9E19CD0A; Sun, 10 May 2026 03:35:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384123; cv=none; b=FgOdG8DjRuYAF7lKpdyM7tvMht2cytem038qlW5gnm3qDo2SoR2bJgQZZJqCNJ4FOXh+bFaZ3I4defryCPq8guaubH9Dce62A6tgtConzNvsIoEgfO1E8GCc+HPSd4nQcWpJAQ7j9CpKZFO58nuetcXZfRiBSjlZ6VkyeLMnlGk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384123; c=relaxed/simple; bh=TZwDsTO9sh2McUwRGVLm7S6JSoP+9wCVBV1doMqj5V8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=jkCnIlC61KLdhne1HN9MoFsge5H2QwPmtScT20Z9FMRgVwjRWjbIdHfnF4tWBX7L+fQ9otgw6vnM59CeAHjvRYKtBtEDnXJvdN1bc6YlN8EUlUAAbBCAofy6wMj8U5SmyGVTKfBivcWqjfsAEhyZRAMGq6icCa9RBGKGPQ9N6tI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=D4IGZZtj; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="D4IGZZtj" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 23D4CC2BCF6; Sun, 10 May 2026 03:35:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384122; bh=TZwDsTO9sh2McUwRGVLm7S6JSoP+9wCVBV1doMqj5V8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=D4IGZZtjz384AqwNbANXdmTZFTKZlplUEM5gIFEOJoEY20GB37gUSeHCxyq+K1HTY nMCb0V+iFLfVvoc1qDgVGgwFChKeB9ESlCXOZCsHPAPP52YWCnC6HfkRAlMScDYzC+ vUtTPbuUhWFlM02vn9DiI749UsTvtmAZQxn342hsBBsGXDFgO9wBglc9bX6zbBeFXF S1gtOGf0/EwHfzLK6/J4hzrseDUtYMiCvH1tCrkyBs7Fuq4lLkajPevbd85yhfcxG4 knr8bxSjEzgfrcGRAXNBzTR0KIkVmMZEXXzOatKx1wR+9mJPXkmYiRPL17rrYNiJ1E QnqgPRKXKofWA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 09/28] perf session: Validate HEADER_ATTR alignment and attr.size before swapping Date: Sun, 10 May 2026 00:34:00 -0300 Message-ID: <20260510033424.255812-10-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Harden PERF_RECORD_HEADER_ATTR handling against crafted perf.data: - Reject unaligned events (header.size not a multiple of u64). - Validate attr.size: must be >=3D PERF_ATTR_SIZE_VER0, a multiple of sizeof(u64), and fit within the event payload. - Copy only min(attr.size, sizeof(struct perf_event_attr)) bytes into a local attr, zeroing the rest so legacy files don't leak adjacent event data into new fields. - Keep the original attr.size so perf_event__synthesize_attr() uses it for both allocation and ID-array placement. Fix perf_event__synthesize_attr() to use attr->size (not the compiled sizeof) for event allocation and layout, so perf inject correctly re-synthesizes attrs from files recorded by a different perf version. Without this, the ID array destination pointer (computed via perf_record_header_attr_id()) would be inconsistent with the allocation when attr->size differs from sizeof. Also fix the parse-no-sample-id-all test to set attr.size, which is now validated, and improve error handling in read_attr() for short reads and invalid attr sizes. Handle ABI0 pipe/inject events where attr.size is 0: use a local attr_size variable set to PERF_ATTR_SIZE_VER0 for both the bounded copy and ID array position, instead of writing back to the event. Native-endian files may be MAP_SHARED (read-only mmap), so writing to the event buffer would SIGSEGV. The swap path handles ABI0 in perf_event__attr_swap() which writes to the MAP_PRIVATE copy. Also add header.size alignment check on the native-endian path (the swap handler already checks this) to reject misaligned events that would produce unaligned u64 ID reads. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/parse-no-sample-id-all.c | 6 ++ tools/perf/util/header.c | 94 +++++++++++++++++++++-- tools/perf/util/session.c | 33 ++++++++ tools/perf/util/synthetic-events.c | 25 +++++- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/p= arse-no-sample-id-all.c index 50e68b7d43aad030..8ac862c94879f3a3 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -82,6 +82,9 @@ static int test__parse_no_sample_id_all(struct test_suite= *test __maybe_unused, .type =3D PERF_RECORD_HEADER_ATTR, .size =3D sizeof(struct test_attr_event), }, + .attr =3D { + .size =3D sizeof(struct perf_event_attr), + }, .id =3D 1, }; struct test_attr_event event2 =3D { @@ -89,6 +92,9 @@ static int test__parse_no_sample_id_all(struct test_suite= *test __maybe_unused, .type =3D PERF_RECORD_HEADER_ATTR, .size =3D sizeof(struct test_attr_event), }, + .attr =3D { + .size =3D sizeof(struct perf_event_attr), + }, .id =3D 2, }; struct perf_record_mmap event3 =3D { diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index f30e48eb3fc32da2..b263f83601842736 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -4770,9 +4770,15 @@ static int read_attr(int fd, struct perf_header *ph, if (sz =3D=3D 0) { /* assume ABI0 */ sz =3D PERF_ATTR_SIZE_VER0; + } else if (sz < PERF_ATTR_SIZE_VER0) { + pr_debug("bad attr size %zu, expected at least %d\n", + sz, PERF_ATTR_SIZE_VER0); + errno =3D EINVAL; + return -1; } else if (sz > our_sz) { pr_debug("file uses a more recent and unsupported ABI" " (%zu bytes extra)\n", sz - our_sz); + errno =3D EINVAL; return -1; } /* what we have not yet read and that we know about */ @@ -4782,11 +4788,21 @@ static int read_attr(int fd, struct perf_header *ph, ptr +=3D PERF_ATTR_SIZE_VER0; =20 ret =3D readn(fd, ptr, left); + if (ret <=3D 0) { + if (ret =3D=3D 0) + errno =3D EIO; + return -1; + } } /* read perf_file_section, ids are read in caller */ ret =3D readn(fd, &f_attr->ids, sizeof(f_attr->ids)); + if (ret <=3D 0) { + if (ret =3D=3D 0) + errno =3D EIO; + return -1; + } =20 - return ret <=3D 0 ? -1 : 0; + return 0; } =20 #ifdef HAVE_LIBTRACEEVENT @@ -5094,11 +5110,40 @@ int perf_event__process_attr(const struct perf_tool= *tool __maybe_unused, union perf_event *event, struct evlist **pevlist) { - u32 i, n_ids; + struct perf_event_attr attr; + u32 i, n_ids, raw_attr_size; u64 *ids; + size_t attr_size, copy_size; struct evsel *evsel; struct evlist *evlist =3D *pevlist; =20 + /* + * HEADER_ATTR event layout (pipe/inject mode): + * + * [header (8 bytes)] [attr (attr_size bytes)] [id0 id1 ... idN] + * |<------------------ header.size --------------------------->| + * + * attr_size varies across perf versions: VER0 =3D 64 bytes, + * current sizeof(struct perf_event_attr) =3D larger. A newer + * producer may emit a larger attr than we understand. + * + * attr.size =3D=3D 0 (ABI0) means the producer didn't set it + * (e.g., bench/inject-buildid, older perf). Treat as VER0. + * + * Require 8-byte alignment so the u64 ID array is aligned + * and attr.size fits cleanly within the payload. + * + * Read attr.size once =E2=80=94 the event may be on a shared mmap + * and re-reading could yield a different value. + */ + raw_attr_size =3D event->attr.attr.size; + if (event->header.size < sizeof(event->header) + PERF_ATTR_SIZE_VER0 || + event->header.size % sizeof(u64) || + (raw_attr_size && (raw_attr_size < PERF_ATTR_SIZE_VER0 || + raw_attr_size % sizeof(u64) || + raw_attr_size > event->header.size - sizeof(event->header)))) + return -EINVAL; + if (dump_trace) perf_event__fprintf_attr(event, stdout); =20 @@ -5108,13 +5153,46 @@ int perf_event__process_attr(const struct perf_tool= *tool __maybe_unused, return -ENOMEM; } =20 - evsel =3D evsel__new(&event->attr.attr); + /* + * attr_size =3D footprint of the attr in the event =E2=80=94 determines + * where the ID array starts. For ABI0, assume VER0 (64 bytes). + * + * copy_size =3D how much we copy into our local struct, capped at + * sizeof(attr) so a newer producer's larger attr doesn't + * overflow. Fields beyond copy_size are zeroed. + * + * Do NOT write attr_size back to the event =E2=80=94 native-endian + * files use MAP_SHARED (read-only), writing would SIGSEGV. + * The swap path handles ABI0 in perf_event__attr_swap() + * which writes to the writable MAP_PRIVATE copy instead. + */ + attr_size =3D raw_attr_size ?: PERF_ATTR_SIZE_VER0; + copy_size =3D min(attr_size, sizeof(attr)); + memcpy(&attr, &event->attr.attr, copy_size); + if (copy_size < sizeof(attr)) + memset((void *)&attr + copy_size, 0, sizeof(attr) - copy_size); + + /* + * Normalize ABI0: the swap path sets attr.size =3D VER0 on the + * event, but the native path leaves it as 0. Set it on the + * local copy so perf inject re-synthesizes with consistent + * layout regardless of endianness. + */ + attr.size =3D attr_size; + + evsel =3D evsel__new(&attr); if (evsel =3D=3D NULL) return -ENOMEM; =20 evlist__add(evlist, evsel); =20 - n_ids =3D event->header.size - sizeof(event->header) - event->attr.attr.s= ize; + /* + * IDs occupy the remainder after header + attr. Use attr_size + * (not copy_size) =E2=80=94 even if the producer's attr is larger than + * our struct, the IDs start after attr_size bytes in the event. + * Validation above guarantees attr_size <=3D payload size. + */ + n_ids =3D event->header.size - sizeof(event->header) - attr_size; n_ids =3D n_ids / sizeof(u64); /* * We don't have the cpu and thread maps on the header, so @@ -5124,7 +5202,13 @@ int perf_event__process_attr(const struct perf_tool = *tool __maybe_unused, if (perf_evsel__alloc_id(&evsel->core, 1, n_ids)) return -ENOMEM; =20 - ids =3D perf_record_header_attr_id(event); + /* + * Locate IDs at attr_size bytes past the attr start in the + * event. Cannot use perf_record_header_attr_id() =E2=80=94 that + * macro reads event->attr.attr.size, which is 0 for ABI0 + * on the native-endian path (no swap handler to fix it up). + */ + ids =3D (void *)&event->attr.attr + attr_size; for (i =3D 0; i < n_ids; i++) { perf_evlist__id_add(&evlist->core, &evsel->core, 0, i, ids[i]); } diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 776061afd568858a..f0b716db75cef7bb 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -618,8 +618,41 @@ do { \ static int perf_event__hdr_attr_swap(union perf_event *event, bool sample_id_all __maybe_unused) { + u32 attr_size, payload_size; size_t size; =20 + /* + * Validate alignment and attr.size (still foreign-endian) + * before calling perf_event__attr_swap(), which uses it via + * bswap_safe() to decide which fields to swap. A crafted + * attr.size larger than the event payload would swap past + * the event boundary and corrupt adjacent memory. + * + * The min_size table guarantees header.size >=3D + * sizeof(header) + PERF_ATTR_SIZE_VER0, so attr.size is + * safe to access. + */ + if (event->header.size % sizeof(u64)) + return -1; + + attr_size =3D bswap_32(event->attr.attr.size); + /* + * ABI0: size field not set. This only happens in pipe/inject + * mode where HEADER_ATTR events carry their own attr. For + * regular perf.data files, read_attr() uses f_header.attr_size + * from the file header instead. Assume PERF_ATTR_SIZE_VER0. + */ + if (!attr_size) + attr_size =3D PERF_ATTR_SIZE_VER0; + payload_size =3D event->header.size - sizeof(event->header); + + if (attr_size < PERF_ATTR_SIZE_VER0 || attr_size % sizeof(u64) || + attr_size > payload_size) { + pr_err("PERF_RECORD_HEADER_ATTR: invalid attr.size %u (min: %d, max: %u,= 8-byte aligned)\n", + attr_size, PERF_ATTR_SIZE_VER0, payload_size); + return -1; + } + perf_event__attr_swap(&event->attr.attr); =20 size =3D event->header.size; diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic= -events.c index 85bee747f4cd2a73..86af854c27acb835 100644 --- a/tools/perf/util/synthetic-events.c +++ b/tools/perf/util/synthetic-events.c @@ -2170,11 +2170,21 @@ int perf_event__synthesize_attr(const struct perf_t= ool *tool, struct perf_event_ u32 ids, u64 *id, perf_event__handler_t process) { union perf_event *ev; - size_t size; + size_t attr_size, size; int err; =20 - size =3D sizeof(struct perf_event_attr); - size =3D PERF_ALIGN(size, sizeof(u64)); + /* + * Use attr->size for the event layout, not the compiled + * sizeof(struct perf_event_attr), so that synthesized events + * match the source perf.data layout. This matters for perf + * inject, which re-synthesizes attrs from a file that may + * have been recorded by a different version of perf. + * perf_record_header_attr_id() locates the ID array at + * attr->size bytes past the attr. + */ + attr_size =3D attr->size ?: sizeof(struct perf_event_attr); + + size =3D PERF_ALIGN(attr_size, sizeof(u64)); size +=3D sizeof(struct perf_event_header); size +=3D ids * sizeof(u64); =20 @@ -2183,7 +2193,14 @@ int perf_event__synthesize_attr(const struct perf_to= ol *tool, struct perf_event_ if (ev =3D=3D NULL) return -ENOMEM; =20 - ev->attr.attr =3D *attr; + /* + * Copy only the bytes we understand; zalloc ensures that any + * extra bytes between sizeof(struct perf_event_attr) and + * attr_size are zero when the source file uses a newer, larger + * struct. + */ + memcpy(&ev->attr.attr, attr, min(sizeof(struct perf_event_attr), attr_siz= e)); + ev->attr.attr.size =3D attr_size; memcpy(perf_record_header_attr_id(ev), id, ids * sizeof(u64)); =20 ev->attr.header.type =3D PERF_RECORD_HEADER_ATTR; --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2E11FDDC5; Sun, 10 May 2026 03:35:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384129; cv=none; b=VmZXc2V6754NONiw4zR9q7u+nn7GpzMtqGHYLgn80E557Gdd2FtdDLXYKMFXUl1kGDcCmGzocnQ9hltk2Grk5tO5JRS6+tmHr5Ua2H4SS2FP4JxaOdg5FUDg8JmnoeJXisiGpQBbA9yPsupoE3n8YxcKo0VfTUdiqtargrKdUzs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384129; c=relaxed/simple; bh=IoaeM52jn5qUeR/gV59h5baTy5QSHI7kw+0uMV86+CU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=mUA9PhTo1eTUlRjOg34XOUpvs5+yUK2gUBWQpcoih4RqnLQ1AZlbg9Y/w2GKiY/1EyJjDpi2drrpP1iGta1/38Fsy5NizhfoO+AqiET3Aec6V6ek+GCDYa1wl7ynigJvSfxBtUkxaHE7zg7IJyzyKyeZBGzU4HbXX6YSy2Mi21I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=SDB0nSqo; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="SDB0nSqo" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 47778C2BCB8; Sun, 10 May 2026 03:35:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384128; bh=IoaeM52jn5qUeR/gV59h5baTy5QSHI7kw+0uMV86+CU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SDB0nSqoWKkSmwq3bkAEX4HxMMye4izIg9GNJ4sKlTfQMiKiCA1IVGZ9MhPx3+2Pg wNtb5QA1KSA6B+HPJB6APqeqSL6l9Bk8juiR2gHrL82nyzSWU0uXfp+CGm2FJwymjp FtwxvPe2cwa9YHKJsw3H4hw9wm/KRkFRlB4M+BCaG8l7Xr10G4MAn36Rja2Sv8tgkU h4ItupLFYFPJ7ynXe5f2iN3WBv9Yp1xh4f9uZ1V00qqodvTTYYMLk63HqO/KDTmPt7 9Auoic8wPdoDHVYBmYsfNnvRlwZe5Yo26NDMtyLA9uq01yaEv5/qKtabFyt+dvdpr8 LYh8mPw7n9LvA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 10/28] perf session: Validate nr fields against event size on both swap and common paths Date: Sun, 10 May 2026 00:34:01 -0300 Message-ID: <20260510033424.255812-11-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Several event types use an nr field to control iteration over variable-length arrays. The swap handlers byte-swap and loop using these fields without bounds checks, and the native processing path trusts them as well. Add bounds checks on both paths for: - PERF_RECORD_THREAD_MAP: validate nr against payload, return -1 on the swap path. On the native path, reject with -EINVAL. - PERF_RECORD_NAMESPACES: clamp nr on the swap path (safe because each entry is indexed by type; missing entries just won't be resolved). Skip the event on the native path. - PERF_RECORD_CPU_MAP: clamp nr for CPUS and MASK sub-types on the swap path. Add bounds checks for mask64 which previously had no nr validation. Skip the event on the native path. - PERF_RECORD_STAT_CONFIG: clamp nr on the swap path (safe because each config entry is self-describing via its tag). Skip the event on the native path. The swap path (cross-endian, writable MAP_PRIVATE mapping) can safely clamp by writing back to the event. The native path (read-only MAP_SHARED mapping) must skip instead of clamping because writing to the mmap'd event would segfault. Also fix stat_config swap range: change size +=3D 1 to size +=3D sizeof(event->stat_config.nr) for clarity. The old +1 happened to work because mem_bswap_64 processes 8-byte chunks, but the intent is to include the 8-byte nr field in the swap range. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 243 +++++++++++++++++++++++++++++++++++--- 1 file changed, 224 insertions(+), 19 deletions(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index f0b716db75cef7bb..fbffa61762cae801 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -491,13 +491,28 @@ static int perf_event__throttle_swap(union perf_event= *event, static int perf_event__namespaces_swap(union perf_event *event, bool sample_id_all) { - u64 i; + u64 i, nr, max_nr; =20 event->namespaces.pid =3D bswap_32(event->namespaces.pid); event->namespaces.tid =3D bswap_32(event->namespaces.tid); event->namespaces.nr_namespaces =3D bswap_64(event->namespaces.nr_namespa= ces); =20 - for (i =3D 0; i < event->namespaces.nr_namespaces; i++) { + nr =3D event->namespaces.nr_namespaces; + /* Cannot underflow: perf_event__min_size[] guarantees header.size >=3D s= izeof */ + max_nr =3D (event->header.size - sizeof(event->namespaces)) / + sizeof(event->namespaces.link_info[0]); + /* + * Safe to clamp: each namespace entry is indexed by type; + * missing entries just won't be resolved. + */ + if (nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " e= xceeds payload (max %" PRIu64 "), clamping\n", + nr, max_nr); + nr =3D max_nr; + event->namespaces.nr_namespaces =3D nr; + } + + for (i =3D 0; i < nr; i++) { struct perf_ns_link_info *ns =3D &event->namespaces.link_info[i]; =20 ns->dev =3D bswap_64(ns->dev); @@ -733,11 +748,23 @@ static int perf_event__auxtrace_error_swap(union perf= _event *event, static int perf_event__thread_map_swap(union perf_event *event, bool sample_id_all __maybe_unused) { - unsigned i; + unsigned int i; + u64 nr; =20 event->thread_map.nr =3D bswap_64(event->thread_map.nr); =20 - for (i =3D 0; i < event->thread_map.nr; i++) + /* + * Reject rather than clamp: unlike namespaces (indexed by type) + * or stat_config (self-describing tags), a truncated thread map + * is structurally broken =E2=80=94 downstream would get a wrong map. + */ + /* Cannot underflow: perf_event__min_size[] guarantees header.size >=3D s= izeof */ + nr =3D event->thread_map.nr; + if (nr > (event->header.size - sizeof(event->thread_map)) / + sizeof(event->thread_map.entries[0])) + return -1; + + for (i =3D 0; i < nr; i++) event->thread_map.entries[i].pid =3D bswap_64(event->thread_map.entries[= i].pid); return 0; } @@ -746,32 +773,80 @@ static int perf_event__cpu_map_swap(union perf_event = *event, bool sample_id_all __maybe_unused) { struct perf_record_cpu_map_data *data =3D &event->cpu_map.data; + u32 payload =3D event->header.size - sizeof(event->header); =20 data->type =3D bswap_16(data->type); =20 + /* + * Safe to clamp: a shorter CPU map just means some CPUs + * are absent; tools process the CPUs that are present. + */ switch (data->type) { - case PERF_CPU_MAP__CPUS: - data->cpus_data.nr =3D bswap_16(data->cpus_data.nr); + case PERF_CPU_MAP__CPUS: { + u16 nr, max_nr; =20 - for (unsigned i =3D 0; i < data->cpus_data.nr; i++) + data->cpus_data.nr =3D bswap_16(data->cpus_data.nr); + nr =3D data->cpus_data.nr; + max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + cpus_data.cpu)) / + sizeof(data->cpus_data.cpu[0]); + if (nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %u= ), clamping\n", + nr, max_nr); + nr =3D max_nr; + data->cpus_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) data->cpus_data.cpu[i] =3D bswap_16(data->cpus_data.cpu[i]); break; + } case PERF_CPU_MAP__MASK: data->mask32_data.long_size =3D bswap_16(data->mask32_data.long_size); =20 switch (data->mask32_data.long_size) { - case 4: + case 4: { + u16 nr, max_nr; + data->mask32_data.nr =3D bswap_16(data->mask32_data.nr); - for (unsigned i =3D 0; i < data->mask32_data.nr; i++) + nr =3D data->mask32_data.nr; + max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + mask32_data.mask)) / + sizeof(data->mask32_data.mask[0]); + if (nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP mask32: nr %u exceeds payload= (max %u), clamping\n", + nr, max_nr); + nr =3D max_nr; + data->mask32_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) data->mask32_data.mask[i] =3D bswap_32(data->mask32_data.mask[i]); break; - case 8: + } + case 8: { + u16 nr, max_nr; + data->mask64_data.nr =3D bswap_16(data->mask64_data.nr); - for (unsigned i =3D 0; i < data->mask64_data.nr; i++) + nr =3D data->mask64_data.nr; + if (payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mas= k)) { + data->mask64_data.nr =3D 0; + break; + } + max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + mask64_data.mask)) / + sizeof(data->mask64_data.mask[0]); + if (nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP mask64: nr %u exceeds payload= (max %u), clamping\n", + nr, max_nr); + nr =3D max_nr; + data->mask64_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) data->mask64_data.mask[i] =3D bswap_64(data->mask64_data.mask[i]); break; + } default: - pr_err("cpu_map swap: unsupported long size\n"); + pr_err("cpu_map swap: unsupported long size %u\n", + data->mask32_data.long_size); } break; case PERF_CPU_MAP__RANGE_CPUS: @@ -787,11 +862,27 @@ static int perf_event__cpu_map_swap(union perf_event = *event, static int perf_event__stat_config_swap(union perf_event *event, bool sample_id_all __maybe_unused) { - u64 size; + u64 nr, max_nr, size; =20 - size =3D bswap_64(event->stat_config.nr) * sizeof(event->stat_config.dat= a[0]); - size +=3D 1; /* nr item itself */ + nr =3D bswap_64(event->stat_config.nr); + /* Cannot underflow: perf_event__min_size[] guarantees header.size >=3D s= izeof */ + max_nr =3D (event->header.size - sizeof(event->stat_config)) / + sizeof(event->stat_config.data[0]); + /* + * Safe to clamp: each config entry is self-describing + * via its tag; missing entries keep their defaults. + */ + if (nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds pay= load (max %" PRIu64 "), clamping\n", + nr, max_nr); + nr =3D max_nr; + } + size =3D nr * sizeof(event->stat_config.data[0]); + /* The swap starts at &nr, so add its size to cover the full range */ + size +=3D sizeof(event->stat_config.nr); mem_bswap_64(&event->stat_config.nr, size); + /* Persist the clamped value in native byte order */ + event->stat_config.nr =3D nr; return 0; } =20 @@ -1729,8 +1820,24 @@ static int machines__deliver_event(struct machines *= machines, "COMM")) return 0; return tool->comm(tool, event, sample, machine); - case PERF_RECORD_NAMESPACES: + case PERF_RECORD_NAMESPACES: { + /* Cannot underflow: perf_event__min_size[] guarantees header.size >=3D = sizeof */ + u64 max_nr =3D (event->header.size - sizeof(event->namespaces)) / + sizeof(event->namespaces.link_info[0]); + + /* + * Native-endian events are mmap'd read-only, so we + * cannot clamp nr in place. Skip the event instead. + * The swap handler already clamps on the writable + * cross-endian path. + */ + if (event->namespaces.nr_namespaces > max_nr) { + pr_warning("WARNING: PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " = exceeds payload (max %" PRIu64 "), skipping\n", + (u64)event->namespaces.nr_namespaces, max_nr); + return 0; + } return tool->namespaces(tool, event, sample, machine); + } case PERF_RECORD_CGROUP: if (!perf_event__check_nul(event->cgroup.path, (void *)event + event->header.size, @@ -1911,15 +2018,112 @@ static s64 perf_session__process_user_event(struct= perf_session *session, perf_session__auxtrace_error_inc(session, event); err =3D tool->auxtrace_error(tool, session, event); break; - case PERF_RECORD_THREAD_MAP: + case PERF_RECORD_THREAD_MAP: { + u64 max_nr; + + if (event->header.size < sizeof(event->thread_map)) { + pr_err("PERF_RECORD_THREAD_MAP: header.size (%u) too small\n", + event->header.size); + err =3D -EINVAL; + break; + } + + max_nr =3D (event->header.size - sizeof(event->thread_map)) / + sizeof(event->thread_map.entries[0]); + if (event->thread_map.nr > max_nr) { + pr_err("PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\= n", + (u64)event->thread_map.nr, max_nr); + err =3D -EINVAL; + break; + } + err =3D tool->thread_map(tool, session, event); break; - case PERF_RECORD_CPU_MAP: + } + case PERF_RECORD_CPU_MAP: { + struct perf_record_cpu_map_data *data =3D &event->cpu_map.data; + u32 payload =3D event->header.size - sizeof(event->header); + + /* + * Native-endian events are mmap'd read-only, so we + * cannot clamp nr fields in place. Skip the event + * if any variant overflows. + */ + switch (data->type) { + case PERF_CPU_MAP__CPUS: { + u16 max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + cpus_data.cpu)) / + sizeof(data->cpus_data.cpu[0]); + + if (data->cpus_data.nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %= u), skipping\n", + data->cpus_data.nr, max_nr); + err =3D 0; + goto out; + } + break; + } + case PERF_CPU_MAP__MASK: + if (data->mask32_data.long_size =3D=3D 4) { + u16 max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + mask32_data.mask)) / + sizeof(data->mask32_data.mask[0]); + + if (data->mask32_data.nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP mask32: nr %u exceeds payloa= d (max %u), skipping\n", + data->mask32_data.nr, max_nr); + err =3D 0; + goto out; + } + } else if (data->mask64_data.long_size =3D=3D 8) { + u16 max_nr; + + if (payload < offsetof(struct perf_record_cpu_map_data, mask64_data.ma= sk)) { + err =3D 0; + goto out; + } + max_nr =3D (payload - offsetof(struct perf_record_cpu_map_data, + mask64_data.mask)) / + sizeof(data->mask64_data.mask[0]); + if (data->mask64_data.nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_CPU_MAP mask64: nr %u exceeds payloa= d (max %u), skipping\n", + data->mask64_data.nr, max_nr); + err =3D 0; + goto out; + } + } else { + pr_warning("WARNING: PERF_RECORD_CPU_MAP: unsupported long_size %u, sk= ipping\n", + data->mask32_data.long_size); + err =3D 0; + goto out; + } + break; + default: + break; + } + err =3D tool->cpu_map(tool, session, event); break; - case PERF_RECORD_STAT_CONFIG: + } + case PERF_RECORD_STAT_CONFIG: { + /* Cannot underflow: perf_event__min_size[] guarantees header.size >=3D = sizeof */ + u64 max_nr =3D (event->header.size - sizeof(event->stat_config)) / + sizeof(event->stat_config.data[0]); + + /* + * Native-endian events are mmap'd read-only, so we + * cannot clamp nr in place. Skip the event instead. + */ + if (event->stat_config.nr > max_nr) { + pr_warning("WARNING: PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds pa= yload (max %" PRIu64 "), skipping\n", + (u64)event->stat_config.nr, max_nr); + err =3D 0; + goto out; + } + err =3D tool->stat_config(tool, session, event); break; + } case PERF_RECORD_STAT: err =3D tool->stat(tool, session, event); break; @@ -1962,6 +2166,7 @@ static s64 perf_session__process_user_event(struct pe= rf_session *session, err =3D -EINVAL; break; } +out: perf_sample__exit(&sample); return err; } --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E5308270EDF; Sun, 10 May 2026 03:35:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384135; cv=none; b=bPQJo9hgRt8Cf7Xa8LJYEN6pmF4b8v131KgZmzZT4GZCwybCuyZCgJx9SZNh3JL7NtCOGQhUv47oOHSoa3V0RbZ9LvLgfvssagbnRHdYvVDHoItvnONBk6iLZz9bUVnewmlu69GFxy5/Q4V6FsYypu0GtD2Cfruut1NrkFdLbTE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384135; c=relaxed/simple; bh=1CRFCSo8NIRq12IlXRBzutnygzIuNVowR2oEY4nRGT8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ev3pHxowjcYSCq4Y3T64h3Xh57/b7ht7TSMpnQtZ44CRBd4DjPOUG95pq0XlxgjHzPxanh7c+mnxlAtS7kabFbiYCf3a1q82x0bytcPDbcoTlTz0OYqHu5vv4/QYleZ/zOedSA1jneq1qYo2uGRzlQk9Oq7+omW7PBuaSkfioUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Bb65LxZB; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Bb65LxZB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 69F55C2BCB8; Sun, 10 May 2026 03:35:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384134; bh=1CRFCSo8NIRq12IlXRBzutnygzIuNVowR2oEY4nRGT8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Bb65LxZB5sxPw7Xl+f+dJpt+y22k84R4vdk+ABUfBHvEoOB+HnTTVa5LedcTSd2m2 tNiqebsDgdwqcaB0vZv5YUcbBXACcsH5SqcmlbBrj897XZfvwJER/m8Xzztz2//OIJ d87yzXmqR8yhoRuLjcoO+/V9PzESJC+rsK8kel6iSAu8I0YvNpPG+pjYe/2GiXI8ly fOCbh6q8EShAIlRvZY6eT1a/Iy2/xMAnm/igKED6nCCVsv2G+Vph4NXW+/4qnjN2Tx gCpVn0OlajGW2x+0c+YRWU4tRdap3NM2wTQo4O5A41c/W9dOdd4zrDT8c6hTDS2WTk CCbVHBXWOxJQQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 11/28] perf header: Byte-swap build ID event pid and bounds check section entries Date: Sun, 10 May 2026 00:34:02 -0300 Message-ID: <20260510033424.255812-12-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo perf_header__read_build_ids() swaps the event header fields for cross-endian perf.data files but not bev.pid. This causes perf_session__findnew_machine() to look up the wrong machine for guest VM build IDs, misattributing them. Swap bev.pid alongside the header fields. Also add a build_id_swap callback for stream-mode build ID events. Harden perf_header__read_build_ids() against crafted perf.data files: - Add overflow check on offset + size to prevent wrap past ULLONG_MAX. - Reject bev.header.size =3D=3D 0 which would loop forever. - Reject bev.header.size > remaining section to prevent reading past the section boundary. - Guard memcmp(filename, "nel.kallsyms]", 13) with len >=3D 13 to avoid reading uninitialized stack memory on short filenames. - Force NUL-termination of filename before passing it to functions like machine__findnew_dso() that use strlen/strcmp. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 50 +++++++++++++++++++++++++++++++++++---- tools/perf/util/session.c | 16 ++++++++++++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index b263f83601842736..f2198ab0defd5804 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include #include "string2.h" #include #include @@ -2578,7 +2579,13 @@ static int perf_header__read_build_ids_abi_quirk(str= uct perf_header *header, } old_bev; struct perf_record_header_build_id bev; char filename[PATH_MAX]; - u64 limit =3D offset + size; + u64 limit; + + /* Prevent offset + size from wrapping past ULLONG_MAX */ + if (size > ULLONG_MAX - offset) + return -1; + + limit =3D offset + size; =20 while (offset < limit) { ssize_t len; @@ -2589,6 +2596,10 @@ static int perf_header__read_build_ids_abi_quirk(str= uct perf_header *header, if (header->needs_swap) perf_event_header__bswap(&old_bev.header); =20 + /* size =3D=3D 0 loops forever; size > remaining reads past section */ + if (old_bev.header.size =3D=3D 0 || old_bev.header.size > limit - offset) + return -1; + len =3D old_bev.header.size - sizeof(old_bev); if (len < 0 || len >=3D PATH_MAX) { pr_warning("invalid build_id filename length %zd\n", len); @@ -2597,6 +2608,13 @@ static int perf_header__read_build_ids_abi_quirk(str= uct perf_header *header, =20 if (readn(input, filename, len) !=3D len) return -1; + /* + * The file data may lack a null terminator, which could + * indicate a corrupt or crafted perf.data file. Ensure + * filename is always a valid C string before passing it + * to functions like machine__findnew_dso(). + */ + filename[len] =3D '\0'; =20 bev.header =3D old_bev.header; =20 @@ -2624,17 +2642,32 @@ static int perf_header__read_build_ids(struct perf_= header *header, struct perf_session *session =3D container_of(header, struct perf_session= , header); struct perf_record_header_build_id bev; char filename[PATH_MAX]; - u64 limit =3D offset + size, orig_offset =3D offset; + u64 limit, orig_offset =3D offset; int err =3D -1; =20 + /* Prevent offset + size from wrapping past ULLONG_MAX */ + if (size > ULLONG_MAX - offset) + return -1; + + limit =3D offset + size; + while (offset < limit) { ssize_t len; =20 if (readn(input, &bev, sizeof(bev)) !=3D sizeof(bev)) goto out; =20 - if (header->needs_swap) + if (header->needs_swap) { perf_event_header__bswap(&bev.header); + bev.pid =3D bswap_32(bev.pid); + } + + /* + * size =3D=3D 0 would loop forever (offset never advances); + * size > remaining would read past the section boundary. + */ + if (bev.header.size =3D=3D 0 || bev.header.size > limit - offset) + goto out; =20 len =3D bev.header.size - sizeof(bev); if (len < 0 || len >=3D PATH_MAX) { @@ -2644,6 +2677,13 @@ static int perf_header__read_build_ids(struct perf_h= eader *header, =20 if (readn(input, filename, len) !=3D len) goto out; + /* + * The file data may lack a null terminator, which could + * indicate a corrupt or crafted perf.data file. Ensure + * filename is always a valid C string before passing it + * to functions like machine__findnew_dso(). + */ + filename[len] =3D '\0'; /* * The a1645ce1 changeset: * @@ -2657,7 +2697,9 @@ static int perf_header__read_build_ids(struct perf_he= ader *header, * '[kernel.kallsyms]' string for the kernel build-id has the * first 4 characters chopped off (where the pid_t sits). */ - if (memcmp(filename, "nel.kallsyms]", 13) =3D=3D 0) { + /* Guard short filenames against memcmp reading past the buffer */ + if (len >=3D (ssize_t)sizeof("nel.kallsyms]") - 1 && + memcmp(filename, "nel.kallsyms]", sizeof("nel.kallsyms]") - 1) =3D= =3D 0) { if (lseek(input, orig_offset, SEEK_SET) =3D=3D (off_t)-1) return -1; return perf_header__read_build_ids_abi_quirk(header, input, offset, siz= e); diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index fbffa61762cae801..c23899c42ef7af34 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -676,6 +676,14 @@ static int perf_event__hdr_attr_swap(union perf_event = *event, return 0; } =20 +static int perf_event__build_id_swap(union perf_event *event, + bool sample_id_all __maybe_unused) +{ + /* Only pid needs swapping =E2=80=94 build_id[] is a raw byte array */ + event->build_id.pid =3D bswap_32(event->build_id.pid); + return 0; +} + static int perf_event__event_update_swap(union perf_event *event, bool sample_id_all __maybe_unused) { @@ -1006,7 +1014,7 @@ static perf_event__swap_op perf_event__swap_ops[] =3D= { [PERF_RECORD_HEADER_ATTR] =3D perf_event__hdr_attr_swap, [PERF_RECORD_HEADER_EVENT_TYPE] =3D perf_event__event_type_swap, [PERF_RECORD_HEADER_TRACING_DATA] =3D perf_event__tracing_data_swap, - [PERF_RECORD_HEADER_BUILD_ID] =3D NULL, + [PERF_RECORD_HEADER_BUILD_ID] =3D perf_event__build_id_swap, [PERF_RECORD_HEADER_FEATURE] =3D perf_event__header_feature_swap, [PERF_RECORD_ID_INDEX] =3D perf_event__all64_swap, [PERF_RECORD_AUXTRACE_INFO] =3D perf_event__auxtrace_info_swap, @@ -1993,6 +2001,12 @@ static s64 perf_session__process_user_event(struct p= erf_session *session, err =3D tool->tracing_data(tool, session, event); break; case PERF_RECORD_HEADER_BUILD_ID: + if (!perf_event__check_nul(event->build_id.filename, + (void *)event + event->header.size, + "HEADER_BUILD_ID")) { + err =3D 0; + break; + } err =3D tool->build_id(tool, session, event); break; case PERF_RECORD_FINISHED_ROUND: --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F5B224677F; Sun, 10 May 2026 03:35:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384140; cv=none; b=StDpwT/yhcCNYwXXGx14P1Pg72LgGSWy5C8Qd0UuJFp71//+SpQohf/CcjSwmZ6Q1YatIl7Xx8WyJjk6V/lc3BqylZsR+WyYx2LvvDageISV8eWljz6xguNfiL0SXvxIdhfa1yvfcqyjWx7lHRXlDhAYsH477iweVPr9VyuCzMU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384140; c=relaxed/simple; bh=CqFwPdskAmw+P1xkh14kA1xCrBzke1eMXGS0ToHyLos=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=T+qDenfus9o1qaUIHQ5zWLCaHzjK7/yRx0M4MjfV8F4qXWZ0f0ZptJ+0QapUvMLqYVZJogqiOCBcee0DaTyHRSonf5qL7PX87epaq6fiRIausD02hfXos//hBN0YPZjP0CCOg8+WJZVtwfbU9EHVZZaA8uiFi4sjd/0dEKrmnZk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=StAnZMfF; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="StAnZMfF" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1BC0EC2BCB8; Sun, 10 May 2026 03:35:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384140; bh=CqFwPdskAmw+P1xkh14kA1xCrBzke1eMXGS0ToHyLos=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=StAnZMfF/TcbifprWZbBPHEAcKNN+lecb+uSgufnqXKF2uRPbrapVW063HYjF7Idp 2Nrl/wCRXxCw+IQxwk30OjjBGOY2ARe7kqXzKHVs6qp8XGobmpQzo4dg90sf7qx/tS yZJ6M1zTbP241cYLC7Qwv35fv6tnvByUo643Mgdj7jQJ1YkIDh0Ns7Nkgov9W+cNrI +7Maj8KbmSZ6Dl0Jd8IeQhrEMOuMKtDeLTAmdUC2l7wvFb4xQGTWB+KVEoMXEnqZox ZJRuBXXarXM65ChJt2DgIcqGYWlDhxjf4brmFuaNRmgdQ2Kx5FDSc9jb6qP6OhERst V0dYgGnPaPSbw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 12/28] perf cpumap: Reject RANGE_CPUS with start_cpu > end_cpu Date: Sun, 10 May 2026 00:34:03 -0300 Message-ID: <20260510033424.255812-13-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo cpu_map__from_range() computes nr_cpus as end_cpu - start_cpu + 1. When a crafted perf.data has start_cpu > end_cpu, this wraps to a huge value, causing perf_cpu_map__empty_new() to attempt a massive allocation. Return NULL when the range is inverted. Also clamp any_cpu to boolean (0 or 1) since it is added to the allocation count =E2=80=94 a crafted value > 1 would inflate the map size. Harden cpu_map__from_mask() to reject unsupported long_size values (anything other than 4 or 8), preventing misinterpretation of the mask data layout. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/cpumap.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 11922e1ded844a03..c32db7b307d7d959 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -93,9 +93,18 @@ static struct perf_cpu_map *cpu_map__from_entries(const = struct perf_record_cpu_m static struct perf_cpu_map *cpu_map__from_mask(const struct perf_record_cp= u_map_data *data) { DECLARE_BITMAP(local_copy, 64); - int weight =3D 0, mask_nr =3D data->mask32_data.nr; + int weight =3D 0, mask_nr; + /* Cache validated long_size =E2=80=94 data is mmap'd and could change */ + u16 long_size; struct perf_cpu_map *map; =20 + /* long_size must be 4 or 8; other values overflow cpus_per_i below */ + if (data->mask32_data.long_size !=3D 4 && data->mask32_data.long_size != =3D 8) + return NULL; + + long_size =3D data->mask32_data.long_size; + mask_nr =3D data->mask32_data.nr; + for (int i =3D 0; i < mask_nr; i++) { perf_record_cpu_map_data__read_one_mask(data, i, local_copy); weight +=3D bitmap_weight(local_copy, 64); @@ -106,11 +115,14 @@ static struct perf_cpu_map *cpu_map__from_mask(const = struct perf_record_cpu_map_ return NULL; =20 for (int i =3D 0, j =3D 0; i < mask_nr; i++) { - int cpus_per_i =3D (i * data->mask32_data.long_size * BITS_PER_BYTE); + int cpus_per_i =3D (i * long_size * BITS_PER_BYTE); int cpu; =20 perf_record_cpu_map_data__read_one_mask(data, i, local_copy); for_each_set_bit(cpu, local_copy, 64) { + /* Guard against more set bits than the first pass counted */ + if (j >=3D weight) + break; if (cpu + cpus_per_i < INT16_MAX) { RC_CHK_ACCESS(map)->map[j++].cpu =3D cpu + cpus_per_i; } else { @@ -129,8 +141,12 @@ static struct perf_cpu_map *cpu_map__from_range(const = struct perf_record_cpu_map struct perf_cpu_map *map; unsigned int i =3D 0; =20 + if (data->range_cpu_data.end_cpu < data->range_cpu_data.start_cpu) + return NULL; + + /* any_cpu is boolean (0 or 1), not a count =E2=80=94 clamp to avoid infl= ated nr */ map =3D perf_cpu_map__empty_new(data->range_cpu_data.end_cpu - - data->range_cpu_data.start_cpu + 1 + data->range_cpu_data.any_cpu); + data->range_cpu_data.start_cpu + 1 + !!data->range_cpu_data.any_cpu); if (!map) return NULL; =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A42722425B; Sun, 10 May 2026 03:35:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384146; cv=none; b=X6QUBMhO6luDBqTprLYnDoSwHd1E0cSt8H8H8wbboY4dZm+sSoYmFbJqBju63SDiz1A83cf4oVp/TWcpQhRB27H0+7cukaZcK3njXVu/XIYJEJBQxCrVDuymrhnA9HhUa8tbW5VTTHcjPRHWwMYRsjDylnAjqV0reEEBxs+Ktj0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384146; c=relaxed/simple; bh=QhY5buQsBzI9eMOfij+AhFf8dCNo+p4JPuYN1WMv8gk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ZD28O9hxJ7194QK1whig6M0/EpSvzAADsluGyOd0K1NWjNhAHlSvTulv5iR+KC94oqXgQqm18XQ62pLDtP5UwXxZklrtH6y3zVF1gXWyvME4z6skfiAqSNCYLcBSmyD8F03UPtG0LhFsuF90K026uyecBWwIfu2PY6NjQzm9PCg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=aICeyhUE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="aICeyhUE" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C4510C2BCF6; Sun, 10 May 2026 03:35:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384146; bh=QhY5buQsBzI9eMOfij+AhFf8dCNo+p4JPuYN1WMv8gk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=aICeyhUEo1Gilv7F8DRX5qMIy2njzEQOr+h6TGx7nbeJpXe3v7emzUdcod18dr1bU tGy/42+bauqsUWdwHqhRIud751vaLKxXPD/UoxvLHltULXeaxYzPTHmnf/NzlV5Ysb gNgFZXrQTGHuW3xAUg8o3Ra3FjXnsfdjshNraW7hQ6gSP8R9BKmuV+vcwQjZP0IZ5Q FGm4O89jxbkf5HA1ZYR7JkrgA7piynEYEoOcwyKkwv2ePNt6sJNtFxVQF+MDDzi+/2 6wYx8vPaONiJWP91ldv/QravwpLbAfQiZDtbM2wfz5wSBrRs17V1bN8gGMxRJ0G0Q5 rW0E546tCC9wQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 13/28] perf auxtrace: Harden auxtrace_error event handling Date: Sun, 10 May 2026 00:34:04 -0300 Message-ID: <20260510033424.255812-14-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Fix four issues in PERF_RECORD_AUXTRACE_ERROR handling: 1. auxtrace_error_name() takes a signed int parameter, but e->type is __u32. A crafted value like 0xFFFFFFFF converts to -1, passes the bounds check, and causes a negative array index. Fix by changing the parameter to unsigned int. 2. The msg field is printed via %s without a length bound. The min_size table only guarantees fields up to msg (offset 48), so a truncated event has zero msg bytes within the event boundary. Compute the available msg length from header.size, cap at sizeof(e->msg), and use %.*s. 3. fmt >=3D 2 adds machine_pid and vcpu fields after msg[64]. Older files may have fmt >=3D 2 but an event size that doesn't include these fields. Add a size check in the swap handler to downgrade fmt before the conditional field access, and a matching size guard in the fprintf path for native-endian events (which are mmap'd read-only and can't be modified in place). 4. python_process_auxtrace_error() had the same issues: msg was passed to tuple_set_string() unbounded, and machine_pid/vcpu were accessed unconditionally without checking fmt or event size. Apply the same bounds checks. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Adrian Hunter Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/auxtrace.c | 24 +++++++++++++--- .../scripting-engines/trace-event-python.c | 28 +++++++++++++++++-- tools/perf/util/session.c | 18 ++++++++++-- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index a224687ffbc1b5be..d9770e1d2f959fc4 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -1759,7 +1759,7 @@ static const char * const auxtrace_error_type_name[] = =3D { [PERF_AUXTRACE_ERROR_ITRACE] =3D "instruction trace", }; =20 -static const char *auxtrace_error_name(int type) +static const char *auxtrace_error_name(unsigned int type) { const char *error_type_name =3D NULL; =20 @@ -1775,6 +1775,7 @@ size_t perf_event__fprintf_auxtrace_error(union perf_= event *event, FILE *fp) struct perf_record_auxtrace_error *e =3D &event->auxtrace_error; unsigned long long nsecs =3D e->time; const char *msg =3D e->msg; + int msg_max; int ret; =20 ret =3D fprintf(fp, " %s error type %u", @@ -1792,11 +1793,26 @@ size_t perf_event__fprintf_auxtrace_error(union per= f_event *event, FILE *fp) if (!e->fmt) msg =3D (const char *)&e->time; =20 - if (e->fmt >=3D 2 && e->machine_pid) + /* Bound msg to the bytes actually within the event, capped at the array = size */ + msg_max =3D (int)((void *)event + event->header.size - (void *)msg); + if (msg_max < 0) + msg_max =3D 0; + if (msg_max > (int)sizeof(e->msg)) + msg_max =3D sizeof(e->msg); + + /* + * Unlike the swap path which downgrades fmt in place, + * native-endian events are mmap'd read-only =E2=80=94 check size + * instead to avoid accessing machine_pid/vcpu OOB. + */ + if (e->fmt >=3D 2 && + event->header.size >=3D offsetof(typeof(event->auxtrace_error), vcpu)= + + sizeof(event->auxtrace_error.vcpu) && + e->machine_pid) ret +=3D fprintf(fp, " machine_pid %d vcpu %d", e->machine_pid, e->vcpu); =20 - ret +=3D fprintf(fp, " cpu %d pid %d tid %d ip %#"PRI_lx64" code %u: %s\n= ", - e->cpu, e->pid, e->tid, e->ip, e->code, msg); + ret +=3D fprintf(fp, " cpu %d pid %d tid %d ip %#"PRI_lx64" code %u: %.*s= \n", + e->cpu, e->pid, e->tid, e->ip, e->code, msg_max, msg); return ret; } =20 diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools= /perf/util/scripting-engines/trace-event-python.c index 5a30caaec73ef06b..5b8f629fd54cbe49 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -1614,6 +1614,9 @@ static void python_process_auxtrace_error(struct perf= _session *session __maybe_u const char *handler_name =3D "auxtrace_error"; unsigned long long tm =3D e->time; const char *msg =3D e->msg; + s32 machine_pid =3D 0, vcpu =3D 0; + char msg_buf[MAX_AUXTRACE_ERROR_MSG + 1]; + int msg_max; PyObject *handler, *t; =20 handler =3D get_handler(handler_name); @@ -1625,6 +1628,25 @@ static void python_process_auxtrace_error(struct per= f_session *session __maybe_u msg =3D (const char *)&e->time; } =20 + /* Bound msg to the bytes within the event, ensure NUL-termination */ + msg_max =3D (int)((void *)event + event->header.size - (void *)msg); + if (msg_max <=3D 0) { + msg_buf[0] =3D '\0'; + } else { + if (msg_max > (int)sizeof(msg_buf) - 1) + msg_max =3D sizeof(msg_buf) - 1; + memcpy(msg_buf, msg, msg_max); + msg_buf[msg_max] =3D '\0'; + } + + /* Only access fmt >=3D 2 fields if the event is large enough */ + if (e->fmt >=3D 2 && + event->header.size >=3D offsetof(typeof(event->auxtrace_error), vcpu)= + + sizeof(event->auxtrace_error.vcpu)) { + machine_pid =3D e->machine_pid; + vcpu =3D e->vcpu; + } + t =3D tuple_new(11); =20 tuple_set_u32(t, 0, e->type); @@ -1634,10 +1656,10 @@ static void python_process_auxtrace_error(struct pe= rf_session *session __maybe_u tuple_set_s32(t, 4, e->tid); tuple_set_u64(t, 5, e->ip); tuple_set_u64(t, 6, tm); - tuple_set_string(t, 7, msg); + tuple_set_string(t, 7, msg_buf); tuple_set_u32(t, 8, cpumode); - tuple_set_s32(t, 9, e->machine_pid); - tuple_set_s32(t, 10, e->vcpu); + tuple_set_s32(t, 9, machine_pid); + tuple_set_s32(t, 10, vcpu); =20 call_object(handler, t, handler_name); =20 diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index c23899c42ef7af34..a2dba77c6a2b9d2f 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -747,8 +747,22 @@ static int perf_event__auxtrace_error_swap(union perf_= event *event, if (event->auxtrace_error.fmt) event->auxtrace_error.time =3D bswap_64(event->auxtrace_error.time); if (event->auxtrace_error.fmt >=3D 2) { - event->auxtrace_error.machine_pid =3D bswap_32(event->auxtrace_error.mac= hine_pid); - event->auxtrace_error.vcpu =3D bswap_32(event->auxtrace_error.vcpu); + /* + * fmt >=3D 2 adds machine_pid and vcpu after msg[64]. + * Older files may have fmt >=3D 2 but an event size + * that doesn't include these fields =E2=80=94 downgrade to + * avoid swapping out of bounds. + */ + if (event->header.size < offsetof(typeof(event->auxtrace_error), vcpu) + + sizeof(event->auxtrace_error.vcpu)) { + pr_warning("WARNING: PERF_RECORD_AUXTRACE_ERROR: fmt %u but event too s= mall for machine_pid/vcpu (%u bytes), downgrading fmt\n", + event->auxtrace_error.fmt, + event->header.size); + event->auxtrace_error.fmt =3D 1; + } else { + event->auxtrace_error.machine_pid =3D bswap_32(event->auxtrace_error.ma= chine_pid); + event->auxtrace_error.vcpu =3D bswap_32(event->auxtrace_error.vcpu); + } } return 0; } --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D0FAD19CD0A; Sun, 10 May 2026 03:35:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384152; cv=none; b=SJHNmW9VYBb2QhAaln3MNwELoYZ7rPZHi/UtTxPvE2DNmhi2BXDipIqZHCpVuQhoNGbS/9+Js7alUNKDJhkiSHo9PeRr2uORz50rILkN8wL5l/YxnxG1Ao8CTHQQTAaJVBLyXknlRtz5TpT8supPWHw/vJd3Uou1ZQFTSsj2otw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384152; c=relaxed/simple; bh=t02wNdgSfNbf5loBv1JRfR4V7UwPBFip15dJG7QRtUQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=sYCSquVtnZHSRlj7d9VmJ2iKqUTTRG3UPXyn6jKKWMlvZYbf+oSPfpymiMVRoabLjE88lB5ue9hUpZXsVjeP9QDwO1uPEbBDYb0398rAJ3kgyug0wrrlJiiipihJGHq5tOh/U8+upcyFlMDtPub9qE4iBz8X/KiOf5XBLQuvV0I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XXcSSEAV; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XXcSSEAV" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CDACFC2BCFB; Sun, 10 May 2026 03:35:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384152; bh=t02wNdgSfNbf5loBv1JRfR4V7UwPBFip15dJG7QRtUQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XXcSSEAVcmaaAsfuA1O9XMbc2uzoxQ2wWweOkOdLV3Aah07QQexEBO1wSl+OyVhQm EEvvThEtYErvBDH7BWMauOdTeTXP8d9IWXHiOWKNdBlVF5hMKtscP3qNj2+FMGra+V 0u4y7WExEgiJjuHWNi1G2fHCzWjUKn+UrXW4aWlJ0Gn/9cZGTOtKRQ7X63BOQW1axW mvBMrUyI5Rpc4IdLER9RlNqlWROKLZe7JYaHS6lM43E5A3WDZ3sE/1I6bJ6AR1+sqc J271q+JailLCn3SKL3oC18V/eT3dQTTBu9sWMdLYdHsoi3oDDFAp95W2PZnM0TTBxq MoUCgt78a0Ypg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, Blake Jones , "Claude Opus 4.6 (1M context)" Subject: [PATCH 14/28] perf session: Add byte-swap and bounds check for PERF_RECORD_BPF_METADATA events Date: Sun, 10 May 2026 00:34:05 -0300 Message-ID: <20260510033424.255812-15-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo PERF_RECORD_BPF_METADATA has no entry in perf_event__swap_ops[], so its nr_entries field is never byte-swapped when reading a cross-endian perf.data file. Downstream processing in perf_event__fprintf_bpf_metadata() loops over nr_entries, so a foreign-endian value causes out-of-bounds reads. Add a swap handler that byte-swaps nr_entries after validating that header.size is large enough. The entries[] array contains only char arrays (key/value strings), so no per-entry swap is needed =E2=80=94 but ensure NUL-termination on the writable cross-endian path. Validate header.size, nr_entries, and string NUL-termination in the common event delivery path so that native-endian files with malicious values are also rejected. Fixes: ab38e84ba9a8 ("perf record: collect BPF metadata from existing BPF p= rograms") Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Blake Jones Cc: Ian Rogers Cc: Jiri Olsa Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 83 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index a2dba77c6a2b9d2f..876e20c4ba8a7808 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -942,6 +942,45 @@ static int perf_event__time_conv_swap(union perf_event= *event, return 0; } =20 +static int perf_event__bpf_metadata_swap(union perf_event *event, + bool sample_id_all __maybe_unused) +{ + u64 i, nr, max_nr; + + /* Fixed header must fit before accessing nr_entries or prog_name */ + if (event->header.size < sizeof(event->bpf_metadata)) + return -1; + + event->bpf_metadata.nr_entries =3D bswap_64(event->bpf_metadata.nr_entrie= s); + + /* + * Ensure NUL-termination on the cross-endian path where the + * mapping is writable (MAP_PRIVATE + PROT_WRITE). Fixing + * the string in place is preferred over rejecting because it + * preserves the event for downstream processing =E2=80=94 only the + * last byte is lost. + * + * The native-endian path (MAP_SHARED + PROT_READ) cannot + * write, so it validates and skips unterminated events in + * perf_session__process_user_event() instead. The two + * strategies produce different outcomes for the same + * malformed input (fix vs skip), which is inherent in the + * writable-vs-read-only mapping model. + */ + event->bpf_metadata.prog_name[BPF_PROG_NAME_LEN - 1] =3D '\0'; + + nr =3D event->bpf_metadata.nr_entries; + max_nr =3D (event->header.size - sizeof(event->bpf_metadata)) / + sizeof(event->bpf_metadata.entries[0]); + if (nr > max_nr) + nr =3D max_nr; + + for (i =3D 0; i < nr; i++) { + event->bpf_metadata.entries[i].key[BPF_METADATA_KEY_LEN - 1] =3D '\0'; + event->bpf_metadata.entries[i].value[BPF_METADATA_VALUE_LEN - 1] =3D '\0= '; + } + return 0; +} static int perf_event__schedstat_cpu_swap(union perf_event *event __maybe_unused, bool sample_id_all __maybe_unused) @@ -1041,6 +1080,7 @@ static perf_event__swap_op perf_event__swap_ops[] =3D= { [PERF_RECORD_STAT_ROUND] =3D perf_event__stat_round_swap, [PERF_RECORD_EVENT_UPDATE] =3D perf_event__event_update_swap, [PERF_RECORD_TIME_CONV] =3D perf_event__time_conv_swap, + [PERF_RECORD_BPF_METADATA] =3D perf_event__bpf_metadata_swap, [PERF_RECORD_SCHEDSTAT_CPU] =3D perf_event__schedstat_cpu_swap, [PERF_RECORD_SCHEDSTAT_DOMAIN] =3D perf_event__schedstat_domain_swap, [PERF_RECORD_HEADER_MAX] =3D NULL, @@ -2181,9 +2221,50 @@ static s64 perf_session__process_user_event(struct p= erf_session *session, case PERF_RECORD_FINISHED_INIT: err =3D tool->finished_init(tool, session, event); break; - case PERF_RECORD_BPF_METADATA: + case PERF_RECORD_BPF_METADATA: { + u64 max_entries; + + if (event->header.size < sizeof(event->bpf_metadata)) { + pr_warning("WARNING: PERF_RECORD_BPF_METADATA: header.size (%u) too sma= ll, skipping\n", + event->header.size); + err =3D 0; + break; + } + + /* + * Native-endian files are mmap'd read-only =E2=80=94 validate + * NUL-termination instead of writing. + */ + if (strnlen(event->bpf_metadata.prog_name, + BPF_PROG_NAME_LEN) =3D=3D BPF_PROG_NAME_LEN) { + pr_warning("WARNING: PERF_RECORD_BPF_METADATA: prog_name not null-termi= nated, skipping\n"); + err =3D 0; + break; + } + + max_entries =3D (event->header.size - sizeof(event->bpf_metadata)) / + sizeof(event->bpf_metadata.entries[0]); + if (event->bpf_metadata.nr_entries > max_entries) { + pr_warning("WARNING: PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " e= xceeds max %" PRIu64 ", skipping\n", + (u64)event->bpf_metadata.nr_entries, max_entries); + err =3D 0; + break; + } + + for (u64 i =3D 0; i < event->bpf_metadata.nr_entries; i++) { + if (strnlen(event->bpf_metadata.entries[i].key, + BPF_METADATA_KEY_LEN) =3D=3D BPF_METADATA_KEY_LEN || + strnlen(event->bpf_metadata.entries[i].value, + BPF_METADATA_VALUE_LEN) =3D=3D BPF_METADATA_VALUE_LEN) { + pr_warning("WARNING: PERF_RECORD_BPF_METADATA: entry %" PRIu64 " key/v= alue not null-terminated, skipping\n", i); + err =3D 0; + goto out; + } + } + err =3D tool->bpf_metadata(tool, session, event); break; + } case PERF_RECORD_SCHEDSTAT_CPU: err =3D tool->schedstat_cpu(tool, session, event); break; --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B78D8230BD9; Sun, 10 May 2026 03:35:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384158; cv=none; b=u+VMB4yQ9gLhWSu8m3mYIm+gfp7jQnEppl/Gia1YNdneCGFNf9iY9S5+pNuK6+Bb52sveqV35S4G1Uixh0ScOacjciZnZjNkT+iFG/FHRXLcTRhf+U/xBMsrgGZyyN/yijmsuA+uiWNfycv4JO4TtOygt1xej4+B9xY1uusw0uI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384158; c=relaxed/simple; bh=41hfN6ZL0GLeujTX0ORGr+PiJjnsi9CiVLFNqJwl7Fg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fPaJEiCZlvWfNSyt679+KJ6KsW9qtDjDrSJwOXWaW4muATCa6qWnJsrRV+8php+G46rSoIX+wOr+NvbgYnIIoLcr7F+icpmhy+8/bHGhomNvSLdgw/f2/Hq684a6HTnTBi/4XvzYGw5X5NLA4VhfA32I925lW7SBebg25obnl5c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZyCN3bDx; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZyCN3bDx" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 62904C2BCB8; Sun, 10 May 2026 03:35:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384158; bh=41hfN6ZL0GLeujTX0ORGr+PiJjnsi9CiVLFNqJwl7Fg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZyCN3bDxM2QRdGyPxoCfWH5Cv6xUbEr4r8oRlMQ/rTi+CpWNFKL+ccDY91SPHTN4K Mzh59T6itMLi7oYdfsYF5hImS+Icu3jsE6iwg4CK74cs9VhqvGuB92aFYJPRaEY7Mf O9QQtVqKetde7VRkSRSAoCPk6vGl3sdOmzfQDDm2lbFNoSBVk0pY9j+mFVYSsFLvd5 dmUrDLyecW93XlcR1HBJeTvNAGvCbASST4mYoGylx6BT2GaMvILA2hSqN/MMCHgTnF s8ziu598pC54FuOq0ZTYkiY0Y8wVUoTkqQ+uXqlJvkCk0tXlELkDOklF/yl8fieUIg 5IIsS/VZ7fxMg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 15/28] perf header: Validate null-termination in PERF_RECORD_EVENT_UPDATE string fields Date: Sun, 10 May 2026 00:34:06 -0300 Message-ID: <20260510033424.255812-16-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo strdup(ev->unit) and strdup(ev->name) read until '\0' with no guarantee the string is null-terminated within event->header.size. The dump_trace fprintf path has the same problem with %s. Validate before either path runs =E2=80=94 same class of bug fixed for MMAP/MMAP2/COMM/CGROUP by perf_event__check_nul(). Also harden the event_update swap handler to: - Validate SCALE event size before swapping the double at offset 24, which exceeds the 24-byte min_size. - Validate CPUS event size before accessing the cpu_map type/nr/long_size fields, which also start at the min_size boundary. - Swap CPUS variant fields (type, nr, long_size) so the processing path sees native byte order. Add validation in perf_event__process_event_update() for all event update variants (UNIT, NAME, SCALE, CPUS) before dump_trace or processing. Fix a missing break before the default case in the CPUS switch path. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 126 +++++++++++++++++++++++++++++++++++--- tools/perf/util/session.c | 99 +++++++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 9 deletions(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index f2198ab0defd5804..d253063b581f21e9 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -5117,24 +5117,65 @@ size_t perf_event__fprintf_event_update(union perf_= event *event, FILE *fp) =20 switch (ev->type) { case PERF_EVENT_UPDATE__SCALE: + if (event->header.size < offsetof(struct perf_record_event_update, scale= ) + + sizeof(ev->scale)) { + ret +=3D fprintf(fp, "... scale: (truncated)\n"); + break; + } ret +=3D fprintf(fp, "... scale: %f\n", ev->scale.scale); break; case PERF_EVENT_UPDATE__UNIT: - ret +=3D fprintf(fp, "... unit: %s\n", ev->unit); - break; - case PERF_EVENT_UPDATE__NAME: - ret +=3D fprintf(fp, "... name: %s\n", ev->name); + case PERF_EVENT_UPDATE__NAME: { + size_t str_off =3D offsetof(struct perf_record_event_update, unit); + size_t max_len =3D event->header.size > str_off ? + event->header.size - str_off : 0; + + if (max_len =3D=3D 0 || strnlen(ev->unit, max_len) =3D=3D max_len) { + ret +=3D fprintf(fp, "... %s: (unterminated)\n", + ev->type =3D=3D PERF_EVENT_UPDATE__UNIT ? "unit" : "name"); + break; + } + ret +=3D fprintf(fp, "... %s: %s\n", + ev->type =3D=3D PERF_EVENT_UPDATE__UNIT ? "unit" : "name", + ev->unit); break; - case PERF_EVENT_UPDATE__CPUS: + } + case PERF_EVENT_UPDATE__CPUS: { + size_t cpus_off =3D offsetof(struct perf_record_event_update, cpus); + u32 cpus_payload; + + if (event->header.size < cpus_off + sizeof(__u16) + + sizeof(struct perf_record_range_cpu_map)) { + ret +=3D fprintf(fp, "... cpus: (truncated)\n"); + break; + } + + /* + * Validate nr against payload =E2=80=94 this function may be + * called from the stub handler (dump_trace path) which + * bypasses perf_event__process_event_update() validation. + */ + cpus_payload =3D event->header.size - cpus_off; + if (ev->cpus.cpus.type =3D=3D PERF_CPU_MAP__CPUS && + ev->cpus.cpus.cpus_data.nr > + (cpus_payload - offsetof(struct perf_record_cpu_map_data, cpus_data.= cpu)) / + sizeof(ev->cpus.cpus.cpus_data.cpu[0])) { + ret +=3D fprintf(fp, "... cpus: nr %u exceeds payload\n", + ev->cpus.cpus.cpus_data.nr); + break; + } + ret +=3D fprintf(fp, "... "); =20 map =3D cpu_map__new_data(&ev->cpus.cpus); if (map) { ret +=3D cpu_map__fprintf(map, fp); perf_cpu_map__put(map); - } else + } else { ret +=3D fprintf(fp, "failed to get cpus\n"); + } break; + } default: ret +=3D fprintf(fp, "... unknown type\n"); break; @@ -5267,6 +5308,75 @@ int perf_event__process_event_update(const struct pe= rf_tool *tool __maybe_unused struct evsel *evsel; struct perf_cpu_map *map; =20 + /* + * Validate payload before dump_trace or processing =E2=80=94 both + * paths access variant-specific fields without further checks. + */ + if (ev->type =3D=3D PERF_EVENT_UPDATE__UNIT || + ev->type =3D=3D PERF_EVENT_UPDATE__NAME) { + size_t str_off =3D offsetof(struct perf_record_event_update, unit); + size_t max_len =3D event->header.size - str_off; + + if (max_len =3D=3D 0 || strnlen(ev->unit, max_len) =3D=3D max_len) { + pr_warning("WARNING: PERF_RECORD_EVENT_UPDATE: %s not null-terminated, = skipping\n", + ev->type =3D=3D PERF_EVENT_UPDATE__UNIT ? "unit" : "name"); + return 0; + } + } else if (ev->type =3D=3D PERF_EVENT_UPDATE__SCALE) { + if (event->header.size < offsetof(struct perf_record_event_update, scale= ) + + sizeof(ev->scale)) { + pr_warning("WARNING: PERF_RECORD_EVENT_UPDATE: SCALE payload too small,= skipping\n"); + return 0; + } + } else if (ev->type =3D=3D PERF_EVENT_UPDATE__CPUS) { + size_t cpus_off =3D offsetof(struct perf_record_event_update, cpus); + size_t min_cpus =3D sizeof(__u16) + + sizeof(struct perf_record_range_cpu_map); + u32 cpus_payload; + + if (event->header.size < cpus_off + min_cpus) { + pr_warning("WARNING: PERF_RECORD_EVENT_UPDATE: CPUS payload too small, = skipping\n"); + return 0; + } + + /* + * Validate per-variant nr against the remaining + * payload on the native path =E2=80=94 the swap path clamps + * nr in perf_event__event_update_swap(), but native + * events are read-only and cannot be clamped in place. + * cpu_map__new_data() trusts nr for allocation and + * iteration, so unchecked values cause OOB reads. + */ + cpus_payload =3D event->header.size - cpus_off; + switch (ev->cpus.cpus.type) { + case PERF_CPU_MAP__CPUS: + if (ev->cpus.cpus.cpus_data.nr > + (cpus_payload - offsetof(struct perf_record_cpu_map_data, cpus_data= .cpu)) / + sizeof(ev->cpus.cpus.cpus_data.cpu[0])) { + pr_warning("WARNING: EVENT_UPDATE CPUS: nr %u exceeds payload, skippin= g\n", + ev->cpus.cpus.cpus_data.nr); + return 0; + } + break; + case PERF_CPU_MAP__MASK: + if (ev->cpus.cpus.mask32_data.long_size =3D=3D 4) { + if (ev->cpus.cpus.mask32_data.nr > + (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask32_d= ata.mask)) / + sizeof(ev->cpus.cpus.mask32_data.mask[0])) + return 0; + } else if (ev->cpus.cpus.mask64_data.long_size =3D=3D 8) { + if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask64_da= ta.mask) || + ev->cpus.cpus.mask64_data.nr > + (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask64_d= ata.mask)) / + sizeof(ev->cpus.cpus.mask64_data.mask[0])) + return 0; + } + break; + default: + break; + } + } + if (dump_trace) perf_event__fprintf_event_update(event, stdout); =20 @@ -5296,8 +5406,10 @@ int perf_event__process_event_update(const struct pe= rf_tool *tool __maybe_unused if (map) { perf_cpu_map__put(evsel->core.pmu_cpus); evsel->core.pmu_cpus =3D map; - } else + } else { pr_err("failed to get event_update cpus\n"); + } + break; default: break; } diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 876e20c4ba8a7808..85591ccdc2e8ada3 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -687,8 +687,103 @@ static int perf_event__build_id_swap(union perf_event= *event, static int perf_event__event_update_swap(union perf_event *event, bool sample_id_all __maybe_unused) { - event->event_update.type =3D bswap_64(event->event_update.type); - event->event_update.id =3D bswap_64(event->event_update.id); + struct perf_record_event_update *ev =3D &event->event_update; + + ev->type =3D bswap_64(ev->type); + ev->id =3D bswap_64(ev->id); + + /* + * Swap variant-specific fields so the processing path + * sees native byte order. + */ + if (ev->type =3D=3D PERF_EVENT_UPDATE__SCALE) { + if (event->header.size < offsetof(struct perf_record_event_update, scale= ) + + sizeof(ev->scale)) + return -1; + mem_bswap_64(&ev->scale.scale, sizeof(ev->scale.scale)); + } else if (ev->type =3D=3D PERF_EVENT_UPDATE__CPUS) { + u32 cpus_payload; + struct perf_record_cpu_map_data *data =3D &ev->cpus.cpus; + + /* CPUS fields start at the same offset as scale (union) */ + if (event->header.size < offsetof(struct perf_record_event_update, cpus)= + + sizeof(__u16) + sizeof(struct perf_record_range_cpu_map)) + return -1; + cpus_payload =3D event->header.size - offsetof(struct perf_record_event_= update, cpus); + data->type =3D bswap_16(data->type); + /* + * Full swap including array elements =E2=80=94 same logic as + * perf_event__cpu_map_swap() but scoped to the + * embedded cpu_map_data within EVENT_UPDATE. + */ + switch (data->type) { + case PERF_CPU_MAP__CPUS: { + u16 nr, max_nr; + + data->cpus_data.nr =3D bswap_16(data->cpus_data.nr); + nr =3D data->cpus_data.nr; + max_nr =3D (cpus_payload - offsetof(struct perf_record_cpu_map_data, + cpus_data.cpu)) / + sizeof(data->cpus_data.cpu[0]); + if (nr > max_nr) { + nr =3D max_nr; + data->cpus_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) + data->cpus_data.cpu[i] =3D bswap_16(data->cpus_data.cpu[i]); + break; + } + case PERF_CPU_MAP__MASK: + data->mask32_data.long_size =3D bswap_16(data->mask32_data.long_size); + switch (data->mask32_data.long_size) { + case 4: { + u16 nr, max_nr; + + data->mask32_data.nr =3D bswap_16(data->mask32_data.nr); + nr =3D data->mask32_data.nr; + max_nr =3D (cpus_payload - offsetof(struct perf_record_cpu_map_data, + mask32_data.mask)) / + sizeof(data->mask32_data.mask[0]); + if (nr > max_nr) { + nr =3D max_nr; + data->mask32_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) + data->mask32_data.mask[i] =3D bswap_32(data->mask32_data.mask[i]); + break; + } + case 8: { + u16 nr, max_nr; + + data->mask64_data.nr =3D bswap_16(data->mask64_data.nr); + nr =3D data->mask64_data.nr; + if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask64_da= ta.mask)) { + data->mask64_data.nr =3D 0; + break; + } + max_nr =3D (cpus_payload - offsetof(struct perf_record_cpu_map_data, + mask64_data.mask)) / + sizeof(data->mask64_data.mask[0]); + if (nr > max_nr) { + nr =3D max_nr; + data->mask64_data.nr =3D nr; + } + for (unsigned int i =3D 0; i < nr; i++) + data->mask64_data.mask[i] =3D bswap_64(data->mask64_data.mask[i]); + break; + } + default: + break; + } + break; + case PERF_CPU_MAP__RANGE_CPUS: + data->range_cpu_data.start_cpu =3D bswap_16(data->range_cpu_data.start_= cpu); + data->range_cpu_data.end_cpu =3D bswap_16(data->range_cpu_data.end_cpu); + break; + default: + break; + } + } return 0; } =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8865919CD0A; Sun, 10 May 2026 03:36:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384164; cv=none; b=joJqj5xb4mNFwKsjBO+OTV7Cr6bwfoDhgJNSVyoIwd62vTK+AsyqUHAR+VJXOjdGAAopUat4u4Ic9B+IP4e4Of0EdchzQy88rA1YIs9RXZaW4of9zLwuWhX3/pSHf35nK2rU4scbzRKQQatAHeFA/7O9eQZgEbaA8HA60scahe8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384164; c=relaxed/simple; bh=g0Y25AePfaVvlT1z13iptFD+upIDn5gxEGiOiVznDWA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DpqlXRkHAeaoeB+tLp6hWDLnmzfCCA9HYFV7GT1PAVwFMSS11ykLCmalXodm6tCgxT5mTsjkEZt3/wTkDCC0UiZdECQgBkXmukhQmDO0m8wEmf5oy1tdGY1YJlPAz6YT3pg22uJsYNJAr2eyRjBMHUtekGCMQdePOX1viBgjLIE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=iPwQdI/C; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="iPwQdI/C" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E4994C2BCB8; Sun, 10 May 2026 03:35:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384164; bh=g0Y25AePfaVvlT1z13iptFD+upIDn5gxEGiOiVznDWA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iPwQdI/CGnjxJrJVrb6loE/cemNQ1CFXB3lJa7ZOFxsLiWuTx7o/iMy2DW5F4t7B3 HFhPuNuo4nQdUVuSoKF+kigez49AugN2hUssCm+TuuW+EFQ7H/FFZaEx/krw2MutGd dg6HsSJ8xriWUuSI4Y3nEQGOALSYX6k5Sur18sxvjKI2dFlFRnShd18IMqSYUj2Rv+ lKRWfYM2U2mrrJJ+p+FlZuNvM82DajjWaKbKBGaRmr5utzrBhDZ8sTIoZ3yFsnsca0 vtk1/CvFO0AY1a8py9IcOmuU0pNwInQ9rIh+35GkpJdATqX3Kn+bdi4kACAgaUdcvM oHYjCFL8ueP3w== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 16/28] perf tools: Bounds check perf_event_attr fields against attr.size before printing Date: Sun, 10 May 2026 00:34:07 -0300 Message-ID: <20260510033424.255812-17-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo perf_event_attr__fprintf() accessed all struct fields unconditionally, but attrs from older perf.data files or BPF-captured syscall payloads may have a smaller size than the current struct. Fields beyond the recorded size contain uninitialized or zero-filled data. Add size-guarded macros (PRINT_ATTRn, PRINT_ATTRn_bf) that compare each field's offset against attr->size before accessing it. Guard the bitfield block (disabled, inherit, ... defer_output) with attr_size >=3D 48. These bitfields share a single __u64 at offset 40, which is within PERF_ATTR_SIZE_VER0 for validated perf.data attrs, but BPF-captured attrs from perf trace can have a smaller size when the tracee passes a minimal struct to sys_perf_event_open. Also fix the BPF trace path: when perf trace intercepts sys_perf_event_open via BPF, the program copies PERF_ATTR_SIZE_VER0 bytes when the tracee passes size=3D0, but leaves the size field as 0. Set attr->size to PERF_ATTR_SIZE_VER0 in the augmented syscall handler so the bounds checks match the actual copied size. Reported-by: sashiko-bot@kernel.org # Running on a local machine Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/trace/beauty/perf_event_open.c | 19 ++- tools/perf/util/perf_event_attr_fprintf.c | 140 ++++++++++++++-------- 2 files changed, 109 insertions(+), 50 deletions(-) diff --git a/tools/perf/trace/beauty/perf_event_open.c b/tools/perf/trace/b= eauty/perf_event_open.c index 9f1ed989c7751ec5..fa4578e8389664e9 100644 --- a/tools/perf/trace/beauty/perf_event_open.c +++ b/tools/perf/trace/beauty/perf_event_open.c @@ -76,7 +76,24 @@ static size_t perf_event_attr___scnprintf(struct perf_ev= ent_attr *attr, char *bf =20 static size_t syscall_arg__scnprintf_augmented_perf_event_attr(struct sysc= all_arg *arg, char *bf, size_t size) { - return perf_event_attr___scnprintf((void *)arg->augmented.args->value, bf= , size, arg->trace->show_zeros); + struct perf_event_attr *attr =3D (void *)arg->augmented.args->value; + struct perf_event_attr local_attr; + + /* + * The BPF program copies PERF_ATTR_SIZE_VER0 bytes when the + * tracee passes size=3D0, but leaves the size field as 0. + * Copy to a local so we can fix up size without writing to + * the potentially read-only augmented args buffer. + */ + if (!attr->size) { + memcpy(&local_attr, attr, PERF_ATTR_SIZE_VER0); + memset((void *)&local_attr + PERF_ATTR_SIZE_VER0, 0, + sizeof(local_attr) - PERF_ATTR_SIZE_VER0); + local_attr.size =3D PERF_ATTR_SIZE_VER0; + attr =3D &local_attr; + } + + return perf_event_attr___scnprintf(attr, bf, size, arg->trace->show_zeros= ); } =20 static size_t syscall_arg__scnprintf_perf_event_attr(char *bf, size_t size= , struct syscall_arg *arg) diff --git a/tools/perf/util/perf_event_attr_fprintf.c b/tools/perf/util/pe= rf_event_attr_fprintf.c index 741c3d657a8b6ae7..e7ee87685d635dd7 100644 --- a/tools/perf/util/perf_event_attr_fprintf.c +++ b/tools/perf/util/perf_event_attr_fprintf.c @@ -275,24 +275,55 @@ static void __p_config_id(struct perf_pmu *pmu, char = *buf, size_t size, u32 type #define p_type_id(val) __p_type_id(buf, BUF_SIZE, pmu, val) #define p_config_id(val) __p_config_id(pmu, buf, BUF_SIZE, attr->type, val) =20 -#define PRINT_ATTRn(_n, _f, _p, _a) \ -do { \ - if (_a || attr->_f) { \ - _p(attr->_f); \ - ret +=3D attr__fprintf(fp, _n, buf, priv);\ - } \ +#define PRINT_ATTRn(_n, _f, _p, _a) \ +do { \ + if (attr_size >=3D offsetof(struct perf_event_attr, _f) + \ + sizeof(attr->_f) && \ + (_a || attr->_f)) { \ + _p(attr->_f); \ + ret +=3D attr__fprintf(fp, _n, buf, priv); \ + } \ +} while (0) + +/* bitfield members share an offset; most are within PERF_ATTR_SIZE_VER0 */ +#define PRINT_ATTRn_bf(_n, _f, _p, _a) \ +do { \ + if (_a || attr->_f) { \ + _p(attr->_f); \ + ret +=3D attr__fprintf(fp, _n, buf, priv); \ + } \ } while (0) =20 #define PRINT_ATTRf(_f, _p) PRINT_ATTRn(#_f, _f, _p, false) +#define PRINT_ATTRf_bf(_f, _p) PRINT_ATTRn_bf(#_f, _f, _p, false) =20 int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr, attr__fprintf_f attr__fprintf, void *priv) { struct perf_pmu *pmu =3D perf_pmus__find_by_type(attr->type); + /* + * size =3D=3D 0 means the caller didn't set it; a full struct + * is always in memory here. Attrs from perf.data already + * had size validated (>=3D PERF_ATTR_SIZE_VER0), so they + * never arrive with size =3D=3D 0. + */ + u32 attr_size =3D attr->size ?: sizeof(*attr); char buf[BUF_SIZE]; int ret =3D 0; =20 - if (!pmu && (attr->type =3D=3D PERF_TYPE_HARDWARE || attr->type =3D=3D PE= RF_TYPE_HW_CACHE)) { + /* + * Cap to what we understand: all callers store the attr in a + * buffer of sizeof(*attr) bytes (perf.data read path copies + * min(attr.size, sizeof), BPF augmented path copies into a + * fixed-size value[] array). A spoofed attr->size larger + * than sizeof would cause PRINT_ATTRn to read past the + * actual buffer. + */ + if (attr_size > sizeof(*attr)) + attr_size =3D sizeof(*attr); + + if (!pmu && attr_size >=3D offsetof(struct perf_event_attr, config) + siz= eof(attr->config) && + (attr->type =3D=3D PERF_TYPE_HARDWARE || attr->type =3D=3D PERF_TYPE_= HW_CACHE)) { u32 extended_type =3D attr->config >> PERF_PMU_TYPE_SHIFT; =20 if (extended_type) @@ -306,45 +337,53 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_ev= ent_attr *attr, PRINT_ATTRf(sample_type, p_sample_type); PRINT_ATTRf(read_format, p_read_format); =20 - PRINT_ATTRf(disabled, p_unsigned); - PRINT_ATTRf(inherit, p_unsigned); - PRINT_ATTRf(pinned, p_unsigned); - PRINT_ATTRf(exclusive, p_unsigned); - PRINT_ATTRf(exclude_user, p_unsigned); - PRINT_ATTRf(exclude_kernel, p_unsigned); - PRINT_ATTRf(exclude_hv, p_unsigned); - PRINT_ATTRf(exclude_idle, p_unsigned); - PRINT_ATTRf(mmap, p_unsigned); - PRINT_ATTRf(comm, p_unsigned); - PRINT_ATTRf(freq, p_unsigned); - PRINT_ATTRf(inherit_stat, p_unsigned); - PRINT_ATTRf(enable_on_exec, p_unsigned); - PRINT_ATTRf(task, p_unsigned); - PRINT_ATTRf(watermark, p_unsigned); - PRINT_ATTRf(precise_ip, p_unsigned); - PRINT_ATTRf(mmap_data, p_unsigned); - PRINT_ATTRf(sample_id_all, p_unsigned); - PRINT_ATTRf(exclude_host, p_unsigned); - PRINT_ATTRf(exclude_guest, p_unsigned); - PRINT_ATTRf(exclude_callchain_kernel, p_unsigned); - PRINT_ATTRf(exclude_callchain_user, p_unsigned); - PRINT_ATTRf(mmap2, p_unsigned); - PRINT_ATTRf(comm_exec, p_unsigned); - PRINT_ATTRf(use_clockid, p_unsigned); - PRINT_ATTRf(context_switch, p_unsigned); - PRINT_ATTRf(write_backward, p_unsigned); - PRINT_ATTRf(namespaces, p_unsigned); - PRINT_ATTRf(ksymbol, p_unsigned); - PRINT_ATTRf(bpf_event, p_unsigned); - PRINT_ATTRf(aux_output, p_unsigned); - PRINT_ATTRf(cgroup, p_unsigned); - PRINT_ATTRf(text_poke, p_unsigned); - PRINT_ATTRf(build_id, p_unsigned); - PRINT_ATTRf(inherit_thread, p_unsigned); - PRINT_ATTRf(remove_on_exec, p_unsigned); - PRINT_ATTRf(sigtrap, p_unsigned); - PRINT_ATTRf(defer_callchain, p_unsigned); - PRINT_ATTRf(defer_output, p_unsigned); + /* + * All bitfields share a single __u64 right after read_format. + * BPF-captured attrs from perf trace may have a small size + * when the tracee passes a minimal struct, so skip the + * entire block when it's not covered. + */ + if (attr_size >=3D offsetof(struct perf_event_attr, wakeup_events)) { + PRINT_ATTRf_bf(disabled, p_unsigned); + PRINT_ATTRf_bf(inherit, p_unsigned); + PRINT_ATTRf_bf(pinned, p_unsigned); + PRINT_ATTRf_bf(exclusive, p_unsigned); + PRINT_ATTRf_bf(exclude_user, p_unsigned); + PRINT_ATTRf_bf(exclude_kernel, p_unsigned); + PRINT_ATTRf_bf(exclude_hv, p_unsigned); + PRINT_ATTRf_bf(exclude_idle, p_unsigned); + PRINT_ATTRf_bf(mmap, p_unsigned); + PRINT_ATTRf_bf(comm, p_unsigned); + PRINT_ATTRf_bf(freq, p_unsigned); + PRINT_ATTRf_bf(inherit_stat, p_unsigned); + PRINT_ATTRf_bf(enable_on_exec, p_unsigned); + PRINT_ATTRf_bf(task, p_unsigned); + PRINT_ATTRf_bf(watermark, p_unsigned); + PRINT_ATTRf_bf(precise_ip, p_unsigned); + PRINT_ATTRf_bf(mmap_data, p_unsigned); + PRINT_ATTRf_bf(sample_id_all, p_unsigned); + PRINT_ATTRf_bf(exclude_host, p_unsigned); + PRINT_ATTRf_bf(exclude_guest, p_unsigned); + PRINT_ATTRf_bf(exclude_callchain_kernel, p_unsigned); + PRINT_ATTRf_bf(exclude_callchain_user, p_unsigned); + PRINT_ATTRf_bf(mmap2, p_unsigned); + PRINT_ATTRf_bf(comm_exec, p_unsigned); + PRINT_ATTRf_bf(use_clockid, p_unsigned); + PRINT_ATTRf_bf(context_switch, p_unsigned); + PRINT_ATTRf_bf(write_backward, p_unsigned); + PRINT_ATTRf_bf(namespaces, p_unsigned); + PRINT_ATTRf_bf(ksymbol, p_unsigned); + PRINT_ATTRf_bf(bpf_event, p_unsigned); + PRINT_ATTRf_bf(aux_output, p_unsigned); + PRINT_ATTRf_bf(cgroup, p_unsigned); + PRINT_ATTRf_bf(text_poke, p_unsigned); + PRINT_ATTRf_bf(build_id, p_unsigned); + PRINT_ATTRf_bf(inherit_thread, p_unsigned); + PRINT_ATTRf_bf(remove_on_exec, p_unsigned); + PRINT_ATTRf_bf(sigtrap, p_unsigned); + PRINT_ATTRf_bf(defer_callchain, p_unsigned); + PRINT_ATTRf_bf(defer_output, p_unsigned); + } =20 PRINT_ATTRn("{ wakeup_events, wakeup_watermark }", wakeup_events, p_unsig= ned, false); PRINT_ATTRf(bp_type, p_unsigned); @@ -359,9 +398,12 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_eve= nt_attr *attr, PRINT_ATTRf(sample_max_stack, p_unsigned); PRINT_ATTRf(aux_sample_size, p_unsigned); PRINT_ATTRf(sig_data, p_unsigned); - PRINT_ATTRf(aux_start_paused, p_unsigned); - PRINT_ATTRf(aux_pause, p_unsigned); - PRINT_ATTRf(aux_resume, p_unsigned); + /* aux_{start_paused,pause,resume} are at byte 116, past VER0 */ + if (attr_size >=3D offsetof(struct perf_event_attr, sig_data)) { + PRINT_ATTRf_bf(aux_start_paused, p_unsigned); + PRINT_ATTRf_bf(aux_pause, p_unsigned); + PRINT_ATTRf_bf(aux_resume, p_unsigned); + } =20 return ret; } --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E5CB2836BE; Sun, 10 May 2026 03:36:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384170; cv=none; b=JPiJkOHERFm+wr/5MLt7lOnOdUve6IMh9R5i1JmRbhtobaEiVbhMzRNxCPNFza0v5wLIfr2bSlCCQWzYon7ghJ0aDSYBYmW2VfCwH7KC8cUgRXmIkEPoEJmoa5DLCWjPFVUnKu2c6lt1KZH8xE3AGDEBKY3Nnu8VW9dILhiemMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384170; c=relaxed/simple; bh=hhhvjEOSbPOQCfruUGArO5R26SJR9x/RF3nu2//oJlo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=HIL6fj/7mmZskEQu1b9FXk9F7fJ6d0m44J476YOzmxEeIBuDR9qZ4Ww+PUdduWBbgjb/JaNIuy5gn8IsosjXrdz7PD0r/OLBtYD1z73wX12Ki+dlarqZMP3whqglPbPXB0SinjpqeGaQu4Lbp/Rux6zdtFgq2SzI644XFwrxAq4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=FwXMDQeg; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="FwXMDQeg" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D383CC2BCB8; Sun, 10 May 2026 03:36:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384170; bh=hhhvjEOSbPOQCfruUGArO5R26SJR9x/RF3nu2//oJlo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FwXMDQegDu+q9ntAFv4JBav1WpyYELUEGpp05RrH1JyhiQWoxXk9OjMKw4zNAncjl 2AxvlfugGW2capPu4UrdfGZ4rtqHDuPiHY9Uyj/B7ZhhXc28xEznEu/T/iR3cWnO+0 AUZ0Q6tXsCJLMk0bXax9p5tW6pyvPb7oCj7bJMEu4hRkk0aprIFTD1oGeIpNtuG/l3 NIw/djC7ta112meroKpkm9ctuVvxsBORzgkIR1URLF8RFTcmmlWKch2BI88bqVLa4m 2G0c/VLJa60qoI3xeA4lfPcXl2V5ycFYhMi5ZqA+yiEy99wrPEusNW+JJjIoiNejKU 70OrlFsgzktUQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , "Claude Opus 4.6 (1M context)" Subject: [PATCH 17/28] perf header: Propagate feature section processing errors Date: Sun, 10 May 2026 00:34:08 -0300 Message-ID: <20260510033424.255812-18-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo perf_session__read_header() discards the return value from perf_header__process_sections(), so any error from a feature section processor (process_nrcpus, process_compressed, etc.) is silently ignored and the session opens as if nothing went wrong. This defeats the validation added by subsequent commits in this series: a crafted perf.data that fails a feature section check would still be processed with partially-initialized state. Check the return value and fail the session if any feature section processor returns an error. For truncated files (data.size =3D=3D 0, i.e. recording was interrupted before the header was finalized), skip feature section processing entirely and clear the feature bitmap so tools use their "feature not present" fallbacks instead of accessing uninitialized env fields. Change the feature processor stubs for optional libraries (libtraceevent, libbpf) from returning -1 to returning 0, so that perf.data files containing these features can still be opened on builds without the optional library =E2=80=94 the feature is simply skipped rather than causing a fatal error. Also fix evlist__prepare_tracepoint_events() failure to return -EINVAL instead of -ENOMEM, since the failure is a data validation issue, not an allocation failure. Fixes: 1c0b04d12ae9 ("perf tools: Add perf_session__read_header function") Cc: Jiri Olsa Cc: Ian Rogers Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index d253063b581f21e9..5cbeda0335f1140c 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -2748,8 +2748,9 @@ static int process_tracing_data(struct feat_fd *ff __= maybe_unused, void *data __ =20 return ret < 0 ? -1 : 0; #else - pr_err("ERROR: Trying to read tracing data without libtraceevent support.= \n"); - return -1; + /* Not an error =E2=80=94 the feature is simply unsupported in this build= */ + pr_debug("Tracing data present but libtraceevent not available, skipping.= \n"); + return 0; #endif } =20 @@ -3643,8 +3644,9 @@ static int process_bpf_prog_info(struct feat_fd *ff _= _maybe_unused, void *data _ up_write(&env->bpf_progs.lock); return err; #else - pr_err("ERROR: Trying to read bpf_prog_info without libbpf support.\n"); - return -1; + /* Not an error =E2=80=94 the feature is simply unsupported in this build= */ + pr_debug("BPF prog info present but libbpf not available, skipping.\n"); + return 0; #endif // HAVE_LIBBPF_SUPPORT } =20 @@ -3712,8 +3714,9 @@ static int process_bpf_btf(struct feat_fd *ff __mayb= e_unused, void *data __mayb free(node); return err; #else - pr_err("ERROR: Trying to read btf data without libbpf support.\n"); - return -1; + /* Not an error =E2=80=94 the feature is simply unsupported in this build= */ + pr_debug("BTF data present but libbpf not available, skipping.\n"); + return 0; #endif // HAVE_LIBBPF_SUPPORT } =20 @@ -4900,7 +4903,7 @@ int perf_session__read_header(struct perf_session *se= ssion) struct perf_file_header f_header; struct perf_file_attr f_attr; u64 f_id; - int nr_attrs, nr_ids, i, j, err; + int nr_attrs, nr_ids, i, j, err =3D -ENOMEM; int fd =3D perf_data__fd(data); =20 session->evlist =3D evlist__new(); @@ -4920,6 +4923,8 @@ int perf_session__read_header(struct perf_session *se= ssion) return err; } =20 + err =3D -ENOMEM; + if (perf_file_header__read(&f_header, header, fd) < 0) return -EINVAL; =20 @@ -4997,15 +5002,36 @@ int perf_session__read_header(struct perf_session *= session) lseek(fd, tmp, SEEK_SET); } =20 + /* + * Skip feature section processing for truncated files + * (data.size =3D=3D 0 means recording was interrupted). The + * section table is unreliable in that case, and the event + * data can still be processed without the feature headers. + * Clear the bitmap so has_feat() returns false and tools + * use their "feature not present" fallbacks instead of + * accessing uninitialized env fields. + */ + if (f_header.data.size =3D=3D 0) { + bitmap_zero(header->adds_features, HEADER_FEAT_BITS); + } else { #ifdef HAVE_LIBTRACEEVENT - perf_header__process_sections(header, fd, &session->tevent, - perf_file_section__process); + err =3D perf_header__process_sections(header, fd, &session->tevent, + perf_file_section__process); + if (err < 0) + goto out_delete_evlist; =20 - if (evlist__prepare_tracepoint_events(session->evlist, session->tevent.pe= vent)) - goto out_delete_evlist; + if (evlist__prepare_tracepoint_events(session->evlist, + session->tevent.pevent)) { + err =3D -EINVAL; + goto out_delete_evlist; + } #else - perf_header__process_sections(header, fd, NULL, perf_file_section__proces= s); + err =3D perf_header__process_sections(header, fd, NULL, + perf_file_section__process); + if (err < 0) + goto out_delete_evlist; #endif + } =20 return 0; out_errno: @@ -5014,7 +5040,7 @@ int perf_session__read_header(struct perf_session *se= ssion) out_delete_evlist: evlist__delete(session->evlist); session->evlist =3D NULL; - return -ENOMEM; + return err; } =20 int perf_event__process_feature(const struct perf_tool *tool __maybe_unuse= d, --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 27E12279DC3; Sun, 10 May 2026 03:36:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384178; cv=none; b=mgcBCRRwDx/IDfOTMccjHxo2evXuOa46X4TM7VBEqipGbB1cnEj9IYyiq+tV/jiOyajJRNuqhCXRLokHkI3RW+a9/NVErMTofHwMSkhEQgwsnDSt4kVKAidennVQsK7cZQKHVHKf1G8aeWqUPGH3MwCtzeL4K9lvgzbrUHvs/Lw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384178; c=relaxed/simple; bh=tH3tmp78z7itj/qw5X2HyXxSWIjyUOUgDP8ZA33W1H4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=LBbKTyLY4NNB07r29nBgfKer5+L3H9/GBJ6Y1mYGa+WfjM9SpEgFtH4Bqtu72cIMEIV4QRn2dZLpY/yDc6tI6iig9sNjz2qif3z2FI0sJfJwl8sSuZld/C2CCMj6MD+A5WgNgD29oM6ySk2ncTLMAKuxmA8NNadmetn3mdnXucs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gxS3EbCE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="gxS3EbCE" Received: by smtp.kernel.org (Postfix) with ESMTPSA id AB847C2BCF6; Sun, 10 May 2026 03:36:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384178; bh=tH3tmp78z7itj/qw5X2HyXxSWIjyUOUgDP8ZA33W1H4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gxS3EbCE1ME3B/y+XfdMbNfJxOx9MZTR9xcyYcP4ieS6zsJShW6rdEvXd4UiReNQv kLaK3PIFk6h9fMhWpBKwLLRBbHmkxnWYzZt1QKFW5CI77yAKtt0i4cSte+5gMHMUMP DI8sskHVKersMrLkLt7bBsNmQgDRvySr0KyttgO9TqrOkJct4T9w0XHP+t6lUk3x1f dZ9YRELoy8aXekfJH42NtGVRpoqZNwI5BsSu5AfcL0iBYuzfwlOBBoQ7NGu4DwZI9V PXjsOnduqwyHFc44MN/oUiozPwICNOUMAeqTHbt+2QM0YUzCSD5QzLHy+lRGrHO6ht Wa6tMFh9zstow== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 18/28] perf header: Validate f_attr.ids section before use in perf_session__read_header() Date: Sun, 10 May 2026 00:34:09 -0300 Message-ID: <20260510033424.255812-19-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo perf_session__read_header() reads f_attr.ids.size from the perf.data file and divides it by sizeof(u64) to compute nr_ids, which is declared as int. No validation is performed on the value before it is used to allocate arrays and drive a read loop. On 32-bit architectures, a crafted f_attr.ids.size of 0x100000000 (4 GB) produces nr_ids =3D 0x20000000, but the allocation size 1 * 0x20000000 * 8 overflows size_t to 0, so zalloc(0) returns a valid pointer. The subsequent loop writes 0x20000000 IDs into that zero-length buffer, corrupting the heap. On 64-bit, the u64-to-int truncation silently drops high bits, processing fewer IDs than the file claims. While not exploitable, this is a data integrity issue. Add validation before using f_attr.ids: - Cap nr_attrs (attrs.size / attr_size) to MAX_NR_ATTRS (1 << 16) with overflow-safe u64 comparison before assigning to int - Reject ids.size not aligned to sizeof(u64) - Cap ids.size / sizeof(u64) to MAX_IDS_PER_ATTR (1 << 24) to prevent int truncation and size_t overflow on 32-bit - Reject ids sections that extend past the end of the file, guarded by S_ISREG() so non-regular files (block devices, pipes) are not falsely rejected Also fix perf_header__getbuffer64() to set errno =3D EIO when readn() returns 0 (EOF). Without this, the out_errno path in perf_session__read_header() returns -errno which is 0 (success) on truncated files, causing downstream NULL dereferences. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Jiri Olsa Cc: Ian Rogers Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 78 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 5cbeda0335f1140c..f4008878bd7eda04 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -64,6 +64,25 @@ #include #endif =20 +/* + * nr_ids * sizeof(struct perf_sample_id) must not overflow + * size_t on 32-bit; the struct is ~104 bytes (32-bit) or + * ~184 bytes (64-bit), so 1<<24 (16M) keeps the product + * under 2 GB on 32-bit. + * + * This is a per-attribute cap only =E2=80=94 the total across all + * attributes is not capped because legitimate high-core-count + * workloads (e.g. 5000 tracepoints =C3=97 4096 CPUs) can exceed + * a single-attribute limit. + */ +#define MAX_IDS_PER_ATTR (1 << 24) +/* + * Cap nr_attrs to prevent resource exhaustion from crafted + * files. 65536 is well beyond any real workload (perf stat + * typically uses < 100 events) but prevents u64-to-int + * truncation on the attr count. + */ +#define MAX_NR_ATTRS (1 << 16) #define MAX_BPF_DATA_LEN (256 * 1024 * 1024) #define MAX_BPF_PROGS 131072 #define MAX_CACHE_ENTRIES 32768 @@ -4468,8 +4487,13 @@ int perf_session__inject_header(struct perf_session = *session, static int perf_header__getbuffer64(struct perf_header *header, int fd, void *buf, size_t size) { - if (readn(fd, buf, size) <=3D 0) + ssize_t n =3D readn(fd, buf, size); + + if (n <=3D 0) { + if (n =3D=3D 0) + errno =3D EIO; return -1; + } =20 if (header->needs_swap) mem_bswap_64(buf, size); @@ -4803,6 +4827,8 @@ static int read_attr(int fd, struct perf_header *ph, if (ret <=3D 0) { pr_debug("cannot read %d bytes of header attr\n", PERF_ATTR_SIZE_VER0); + if (ret =3D=3D 0) + errno =3D EIO; return -1; } =20 @@ -4903,6 +4929,7 @@ int perf_session__read_header(struct perf_session *se= ssion) struct perf_file_header f_header; struct perf_file_attr f_attr; u64 f_id; + struct stat input_stat; int nr_attrs, nr_ids, i, j, err =3D -ENOMEM; int fd =3D perf_data__fd(data); =20 @@ -4952,6 +4979,15 @@ int perf_session__read_header(struct perf_session *s= ession) return -EINVAL; } =20 + if (fstat(fd, &input_stat) < 0) + return -errno; + + /* Check before assigning to int to avoid u64-to-int truncation */ + if (f_header.attrs.size / f_header.attr_size > MAX_NR_ATTRS) { + pr_err("Too many attributes: %" PRIu64 " (max %d)\n", + f_header.attrs.size / f_header.attr_size, MAX_NR_ATTRS); + return -EINVAL; + } nr_attrs =3D f_header.attrs.size / f_header.attr_size; lseek(fd, f_header.attrs.offset, SEEK_SET); =20 @@ -4968,6 +5004,45 @@ int perf_session__read_header(struct perf_session *s= ession) perf_event__attr_swap(&f_attr.attr); } =20 + /* + * Validate ids section: must be aligned to u64, and + * the count must fit in an int to avoid truncation in + * nr_ids and size_t overflow in perf_evsel__alloc_id() + * on 32-bit architectures. + */ + if (f_attr.ids.size % sizeof(u64)) { + pr_err("Invalid ids section size %" PRIu64 " for attr %d, not aligned t= o u64\n", + f_attr.ids.size, i); + err =3D -EINVAL; + goto out_delete_evlist; + } + + /* + * Cap the ID count to avoid int truncation of nr_ids + * on 64-bit and size_t overflow in the allocation + * paths (nr_ids * sizeof(u64), nr_ids * + * sizeof(struct perf_sample_id)) on 32-bit. + */ + if (f_attr.ids.size / sizeof(u64) > MAX_IDS_PER_ATTR) { + pr_err("Invalid ids section size %" PRIu64 " for attr %d, too many IDs\= n", + f_attr.ids.size, i); + err =3D -EINVAL; + goto out_delete_evlist; + } + + /* + * FIXME: see perf_header__process_sections() =E2=80=94 block + * devices bypass this check because st_size is 0. + */ + if (S_ISREG(input_stat.st_mode) && + (f_attr.ids.offset > (u64)input_stat.st_size || + f_attr.ids.size > (u64)input_stat.st_size - f_attr.ids.offset)) { + pr_err("Invalid ids section for attr %d: offset=3D%" PRIu64 " size=3D%"= PRIu64 " exceeds file size %" PRIu64 "\n", + i, f_attr.ids.offset, f_attr.ids.size, (u64)input_stat.st_size); + err =3D -EINVAL; + goto out_delete_evlist; + } + tmp =3D lseek(fd, 0, SEEK_CUR); evsel =3D evsel__new(&f_attr.attr); =20 @@ -4982,6 +5057,7 @@ int perf_session__read_header(struct perf_session *se= ssion) evlist__add(session->evlist, evsel); =20 nr_ids =3D f_attr.ids.size / sizeof(u64); + /* * We don't have the cpu and thread maps on the header, so * for allocating the perf_sample_id table we fake 1 cpu and --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2B74A22425B; Sun, 10 May 2026 03:36:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384184; cv=none; b=tjWFSNN6vn7/KOvBESJUkXFL9zDGoBIL4i7Q9t/tMw9pxGXbdwXwLN5f8pFZzom14yW64LtoX/mGvlq/AKAG4DuTiq8Z3H2QOHV1BAN9ysLIB9Qmm+S2jZs0FWA8/3Cf28sM314vfEaC661S8iAPT2iXAqNxTHXkoKm7Age+yHk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384184; c=relaxed/simple; bh=52B0C7dWKgMh9o+uce9gU6WCrJG1z3a6g0/2jvqnA9A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Gi6MY1+jLFPJn+TxwjjhqNXwrs6aL+r/NhxGBil1FcByNzf9YCAEVoOXeL7y56Qn2VDJ1+r9imyfeWyd9uPe9xoXBIyLQnpDEyJP2S9YhhQtoHKd8Vvinp5WBTb4vy14lDqI9JCbJffg3w7/5WjjVfcJMZ4RRc571VU3sPE3vYA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=u+bS2Jb8; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="u+bS2Jb8" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CDB59C2BCF6; Sun, 10 May 2026 03:36:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384183; bh=52B0C7dWKgMh9o+uce9gU6WCrJG1z3a6g0/2jvqnA9A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=u+bS2Jb8CkEpnmYtiXhBK2aLV25AGXfjkG7jl4lghHK/y5/HGqBJXc2Qly5oVeDAF 7NM6aU234KZ4pXHwudvfO/EKC8tTMSAz508pvXgIlfmib5rRZCbmPYr1HtYEpb8qQv Kx9R8ybGqV6yv2RJUuFErEYWPjePQl5VHMv7t0pZso6hf31p31kD7GAPY1wvQsvKNv j/HItzU/zuA0iK3P+C/GoKfIGzJSkvesv6hUF0I1FlPR/RJMtjEg7JTffrmMczGKrm JbqaQXHFdNwnakmKwwAc0jS097k67MYRztMW+r2a+UpuFclrKMIiH2Ywk79uh+A9DK 4VN3ITwTZ6aYw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, David Carrillo-Cisneros , "Claude Opus 4.6 (1M context)" Subject: [PATCH 19/28] perf header: Validate feature section size and add read path bounds checking Date: Sun, 10 May 2026 00:34:10 -0300 Message-ID: <20260510033424.255812-20-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Harden feature section parsing against crafted perf.data files: 1. perf_header__process_sections() reads the feature section table and passes each section's offset and size directly to the processing callbacks without validating them against the actual file size. A crafted section size would make all downstream bounds checks against ff->size ineffective since they compare against the untrusted, inflated bound. Add an fstat() check with S_ISREG() guard and verify that each section's offset + size does not extend past EOF. 2. __do_read_buf() validates reads against ff->size (section size), but __do_read_fd() had no such check, so a malformed perf.data with an understated section size could cause reads past the end of the current section into the next section's data. Add the bounds check in __do_read(), the common caller of both helpers, so it is enforced uniformly for both the fd and buf paths. Track the section-relative offset in __do_read_fd() so the check works for the fd path. Reject negative sizes which on 32-bit can occur when a u32 >=3D 0x80000000 is passed as ssize_t. 3. do_read_string() relied on file data being null-padded. Add explicit null-termination (buf[len-1] =3D '\0') after reading and validate length (>=3D 1, fits within section) before allocating, so callers like process_cpu_topology() never receive an unterminated string. 4. Initialize feat_fd.offset to 0 (section-relative) instead of section->offset (file-absolute) so the bounds tracking is consistent with __do_read()'s section-relative comparison. Adjust process_build_id() to use lseek() for its file-absolute offset needs since it cannot rely on ff->offset for that. 5. Propagate ff->size to perf_file_section__fprintf_info() so its reads are also bounded. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: David Carrillo-Cisneros Cc: Ian Rogers Cc: Jiri Olsa Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 62 ++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index f4008878bd7eda04..a8655a784eaa5ba9 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -233,23 +233,32 @@ static int __do_read_fd(struct feat_fd *ff, void *add= r, ssize_t size) =20 if (ret !=3D size) return ret < 0 ? (int)ret : -1; + ff->offset +=3D size; return 0; } =20 static int __do_read_buf(struct feat_fd *ff, void *addr, ssize_t size) { - if (size > (ssize_t)ff->size - ff->offset) - return -1; - memcpy(addr, ff->buf + ff->offset, size); ff->offset +=3D size; =20 return 0; - } =20 static int __do_read(struct feat_fd *ff, void *addr, ssize_t size) { + /* + * Reject negative sizes, which on 32-bit can occur when a + * u32 >=3D 0x80000000 is passed as ssize_t. The cast to + * ssize_t is safe because perf_header__process_sections() + * validates that each section fits within the file size + * before any feature callback reaches here, and only + * feature sections (metadata like build IDs, topology, etc.) + * use this path =E2=80=94 these cannot legitimately approach 2GB. + */ + if (size < 0 || size > (ssize_t)ff->size - ff->offset) + return -1; + if (!ff->buf) return __do_read_fd(ff, addr, size); return __do_read_buf(ff, addr, size); @@ -289,16 +298,22 @@ static char *do_read_string(struct feat_fd *ff) if (do_read_u32(ff, &len)) return NULL; =20 + /* At least the null terminator. */ + if (len < 1 || len > ff->size - ff->offset) + return NULL; + buf =3D malloc(len); if (!buf) return NULL; =20 if (!__do_read(ff, buf, len)) { /* - * strings are padded by zeroes - * thus the actual strlen of buf - * may be less than len + * do_write_string() writes len including the null + * terminator, padded to NAME_ALIGN. Ensure the + * string is always null-terminated even if the file + * data has been tampered with. */ + buf[len - 1] =3D '\0'; return buf; } =20 @@ -2775,7 +2790,12 @@ static int process_tracing_data(struct feat_fd *ff _= _maybe_unused, void *data __ =20 static int process_build_id(struct feat_fd *ff, void *data __maybe_unused) { - if (perf_header__read_build_ids(ff->ph, ff->fd, ff->offset, ff->size)) + off_t offset =3D lseek(ff->fd, 0, SEEK_CUR); + + if (offset =3D=3D (off_t)-1) + return -1; + + if (perf_header__read_build_ids(ff->ph, ff->fd, offset, ff->size)) pr_debug("Failed to read buildids, continuing...\n"); return 0; } @@ -4152,6 +4172,7 @@ static int perf_file_section__fprintf_info(struct per= f_file_section *section, ff =3D (struct feat_fd) { .fd =3D fd, .ph =3D ph, + .size =3D section->size, }; =20 if (!feat_ops[feat].full_only || hd->full) @@ -4512,6 +4533,7 @@ int perf_header__process_sections(struct perf_header = *header, int fd, int sec_size; int feat; int err; + struct stat st; =20 nr_sections =3D bitmap_weight(header->adds_features, HEADER_FEAT_BITS); if (!nr_sections) @@ -4529,7 +4551,29 @@ int perf_header__process_sections(struct perf_header= *header, int fd, if (err < 0) goto out_free; =20 + if (fstat(fd, &st) < 0) { + pr_err("Failed to stat the perf data file\n"); + err =3D -1; + goto out_free; + } + for_each_set_bit(feat, header->adds_features, header->last_feat) { + /* + * FIXME: block devices have st_size =3D=3D 0, so we skip + * bounds checking entirely. Historically perf never + * prevented using a block device as input, but it + * probably should =E2=80=94 there's no valid use case for it + * and it bypasses all file-size validation. + */ + if (S_ISREG(st.st_mode) && + (sec->offset > (u64)st.st_size || + sec->size > (u64)st.st_size - sec->offset)) { + pr_err("Feature %s (%d) section extends past EOF (offset=3D%" PRIu64 ",= size=3D%" PRIu64 ", file=3D%" PRIu64 ")\n", + header_feat__name(feat), feat, + sec->offset, sec->size, (u64)st.st_size); + err =3D -1; + goto out_free; + } err =3D process(sec++, header, feat, fd, data); if (err < 0) goto out_free; @@ -4756,7 +4800,7 @@ static int perf_file_section__process(struct perf_fil= e_section *section, .fd =3D fd, .ph =3D ph, .size =3D section->size, - .offset =3D section->offset, + .offset =3D 0, }; =20 if (lseek(fd, section->offset, SEEK_SET) =3D=3D (off_t)-1) { --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 736C6282F29; Sun, 10 May 2026 03:36:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384189; cv=none; b=hs7kPdhk5BTlfqNMSTxoUb9feeIT3kklPk9FEcxroorrKJ0laNHdoxUhJoOJt0+gBwesvxSbr+py2qommNnVgQMQ19sir7QpgBxlVhQkNnndjFVEh9xVFlHxx/uIzV+XaIgESoGLlAdy9NXfWmwZYca1icDaNb9JSk175Y+gSNQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384189; c=relaxed/simple; bh=Uo4Aj5hoW5Le1FOXzlcX4O+kIwQ370Wtq6/SPTihl34=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=u255jwqaHGU+q7w5zA6jRHdphUccue6LSJy4bwrZbDxG+Q+Y2iO/mIlI/VK+MiaGW25es4C3FhkTk9E9UK4KlyAGjHI8pRQcJiBhzqtaSuuVcgbgR7g2CnGZ1TJAVPtkFbbDu2ll6Xi/tYb3q/MGmVJJOAr1Mu3wb1jLLjGmw6I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=O7l9hfj7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="O7l9hfj7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 67D0CC2BCB8; Sun, 10 May 2026 03:36:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384189; bh=Uo4Aj5hoW5Le1FOXzlcX4O+kIwQ370Wtq6/SPTihl34=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O7l9hfj7Dp14b8jc6MInjVvey1O+Z883IU0uWN6O0o4Xpfov/9NE0IKPkvqv04l9+ R5lhDK6ywGW3l1QRCd6MIj2A75x2kmhnpnyrzH4RAeQA8q+s82hCDQAMYx/rnjmNg8 C5VpUaGyUEmcSdPglXA/oAWgqI6dU5KTAKE5g6ti0b91ins8mC26Vux63xo+48lsGH U9BjbU1L0HBAhn9PM+wVAxhL+XqaZIsRif7eZm22d26u2EGk5+z4CVdxcgFUd8xazi HG89K6Q6PdGBCQii/yhTTxoyumvHCxIffHzKw07e8JwCMePbsG/iUP7Rfxsw8F2jnQ UiM5pqYh1obvA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, Wang Nan , "Claude Opus 4.6 (1M context)" Subject: [PATCH 20/28] perf header: Sanity check HEADER_EVENT_DESC attr.size before swap Date: Sun, 10 May 2026 00:34:11 -0300 Message-ID: <20260510033424.255812-21-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo read_event_desc() reads nre (event count), sz (attr size), and nr (IDs per event) from the file and uses them to control allocations and loops without validating them against the section size. A crafted perf.data could trigger large allocations or many loop iterations before __do_read() eventually rejects the reads. Add bounds checks in read_event_desc(): - Reject sz smaller than PERF_ATTR_SIZE_VER0. - Require at least one event (nre > 0). - Check that nre events fit in the remaining section, using the minimum per-event footprint of sz + sizeof(u32). - Reject attr->size > sz before calling perf_event__attr_swap() to prevent heap out-of-bounds access. - Check that nr IDs fit in the remaining section before allocating. Fixes: b30b61729246 ("perf tools: Fix a problem when opening old perf.data = with different byte order") Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Wang Nan Cc: Jiri Olsa Cc: Ian Rogers Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 50 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index a8655a784eaa5ba9..0bbe90865e9c1ceb 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -2170,9 +2170,25 @@ static struct evsel *read_event_desc(struct feat_fd = *ff) if (do_read_u32(ff, &nre)) goto error; =20 + /* Size of each of the nre attributes. */ if (do_read_u32(ff, &sz)) goto error; =20 + /* + * Require at least one event with an attr no smaller than the + * first published struct, and reject sz values where + * sz + sizeof(u32) would overflow size_t (possible on 32-bit) + * or nre =3D=3D UINT32_MAX where nre + 1 wraps to 0 in the calloc. + * + * The minimum section footprint per event is sz bytes for the + * attr plus a u32 for the id count, check that nre events fit. + */ + if (!nre || sz < PERF_ATTR_SIZE_VER0 || + sz > ff->size || (size_t)sz > SIZE_MAX - sizeof(u32) || + nre =3D=3D UINT32_MAX || + nre > (ff->size - ff->offset) / (sz + sizeof(u32))) + goto error; + /* buffer to hold on file attr struct */ buf =3D malloc(sz); if (!buf) @@ -2188,6 +2204,9 @@ static struct evsel *read_event_desc(struct feat_fd *= ff) msz =3D sz; =20 for (i =3D 0, evsel =3D events; i < nre; evsel++, i++) { + struct perf_event_attr *attr =3D buf; + u32 attr_size; + evsel->core.idx =3D i; =20 /* @@ -2197,6 +2216,32 @@ static struct evsel *read_event_desc(struct feat_fd = *ff) if (__do_read(ff, buf, sz)) goto error; =20 + /* Reject before attr_swap to prevent OOB via bswap_safe() */ + attr_size =3D ff->ph->needs_swap ? bswap_32(attr->size) : attr->size; + /* ABI0: size =3D=3D 0 means the producer didn't set it */ + if (!attr_size) { + attr_size =3D PERF_ATTR_SIZE_VER0; + /* + * Write back so free_event_desc() doesn't + * treat this event as the end-of-array sentinel + * (it iterates while attr.size !=3D 0). + * + * Only for native =E2=80=94 the swap path must NOT + * write native-endian VER0 here because + * perf_event__attr_swap() would re-swap it + * to 0x40000000, defeating bswap_safe() bounds. + * perf_event__attr_swap() has its own ABI0 + * fallback that sets VER0 after swapping. + */ + if (!ff->ph->needs_swap) + attr->size =3D attr_size; + } + if (attr_size < PERF_ATTR_SIZE_VER0 || attr_size > sz) { + pr_err("Event %d attr.size (%u) invalid (min: %d, max: %u)\n", + i, attr_size, PERF_ATTR_SIZE_VER0, sz); + goto error; + } + if (ff->ph->needs_swap) perf_event__attr_swap(buf); =20 @@ -2218,6 +2263,10 @@ static struct evsel *read_event_desc(struct feat_fd = *ff) if (!nr) continue; =20 + /* Prevent oversized allocation from crafted nr */ + if (nr > (ff->size - ff->offset) / sizeof(*id)) + goto error; + id =3D calloc(nr, sizeof(*id)); if (!id) goto error; @@ -4995,7 +5044,6 @@ int perf_session__read_header(struct perf_session *se= ssion) } =20 err =3D -ENOMEM; - if (perf_file_header__read(&f_header, header, fd) < 0) return -EINVAL; =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CA7E4283FFB; Sun, 10 May 2026 03:36:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384194; cv=none; b=O3RPPdVHrnJFu/PqilyKfmeKHoWJN0knTSuuYhqr31xoyrCge+P76FnfPzLUGbYPvg9UYr3/Vvf3ESOBLlky/WW3aa7nhu9Ka3t3A0Rs5lHUGBWBCVOTWuXcyobcTkKDOmmbLgrt4evCURRVLRNwjZ2FPrqQvNuJV+imoWzZQ+s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384194; c=relaxed/simple; bh=C7MhExysfuRkdz59+MjqEIfo94RCdFXIY10WXyJXAaw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nZ2lcQAugrcywKFv8/hJ7ENfSbv3M6Gj+5CiUjseXaCU5Ug1AYBL2JhQVq+vRyeoaJpeVaikLZhVMdo3YRcr9wjZjQzSWIEI6fxgR0BuEgKbe81MdzdqE4q2UYPVT4bQ+pMeuEBlwbRoA3/sbhBrj76sArU1FoVOvPE4gb2KBZo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NZmXutp8; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="NZmXutp8" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D05C0C2BCF6; Sun, 10 May 2026 03:36:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384194; bh=C7MhExysfuRkdz59+MjqEIfo94RCdFXIY10WXyJXAaw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NZmXutp8FstiGRLQUVAfQU/Nb+y7Wew+beeswVhDkSavIjpyYfbEY0QYHlOqMIzmc NQsg19VQwqTJO/4McpO29USXI/X9MT+D9DgF564AwapM+cJhC3F8uaatSqg+94M+Bj UGFMfB4W1iGj5A84ZyUXQKtBRHiUUFWzOJZ+FMIrMQaWnP4VzDTHKRxu33Tk28KJyb oiCK8pMar67cLb/bcDiYlffokuKRpIx2aZYVC4LgOjsGL0iZsnU6L1C4b5hQWUJHgM Ym7Ymbcn7OfOjFqdVTw5ZW/lbxO/yHJo6zngARD1azrVuvDW3RzPaMfPRrAPK1zJvM YCoL8zTm6vT5Q== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 21/28] perf header: Validate bitmap size before allocating in do_read_bitmap() Date: Sun, 10 May 2026 00:34:12 -0300 Message-ID: <20260510033424.255812-22-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo do_read_bitmap() reads a u64 bit count from the file and passes it to bitmap_zalloc() without checking it against the remaining section size. A crafted perf.data could trigger a large allocation that would only fail later when the per-element reads exceed section bounds. Additionally, bitmap_zalloc() takes an int parameter, so a crafted size with bits set above bit 31 (e.g. 0x100000040) would pass the section bounds check but truncate when passed to bitmap_zalloc(), allocating a much smaller buffer than the subsequent read loop expects. Reject size values that exceed INT_MAX, and check that the data needed (BITS_TO_U64(size) u64 values) fits in the remaining section before allocating. Switch from bitmap_zalloc() to calloc() of u64 units so the allocation size matches the u64 read/write granularity and avoids unsigned long vs u64 mismatch on 32-bit architectures. Fix do_write_bitmap() to cast via u64* rather than reading unsigned long values from the bitmap directly, preventing out-of-bounds reads on 32-bit systems where sizeof(unsigned long) is 4 but the bitmap is stored in u64 units. Fix process_mem_topology() minimum section size: the check used nr * 2 * sizeof(u64) per node, but do_read_bitmap() reads an additional u64 for the bitmap size, so the minimum is 3 * sizeof(u64). Fix memory leak in process_mem_topology() error paths: replace free(nodes) with memory_node__delete_nodes() to free per-node bitmaps allocated by do_read_bitmap(). Currently used by process_mem_topology() for HEADER_MEM_TOPOLOGY. Reported-by: sashiko-bot@kernel.org # Running on a local machine Closes: https://lore.kernel.org/linux-perf-users/20260414224622.2AE69C19425= @smtp.kernel.org/ Fixes: a881fc56038a ("perf header: Sanity check HEADER_MEM_TOPOLOGY") Closes: https://lore.kernel.org/linux-perf-users/20260410223242.DD76FC19421= @smtp.kernel.org/ Cc: Jiri Olsa Cc: Ian Rogers Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 0bbe90865e9c1ceb..bda8705e87648800 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -178,15 +178,25 @@ int do_write(struct feat_fd *ff, const void *buf, siz= e_t size) /* Return: 0 if succeeded, -ERR if failed. */ static int do_write_bitmap(struct feat_fd *ff, unsigned long *set, u64 siz= e) { - u64 *p =3D (u64 *) set; + size_t byte_size =3D BITS_TO_LONGS(size) * sizeof(unsigned long); int i, ret; =20 ret =3D do_write(ff, &size, sizeof(size)); if (ret < 0) return ret; =20 + /* + * The on-disk format uses u64 elements, but the in-memory bitmap + * uses unsigned long, which is only 4 bytes on 32-bit architectures. + * Copy with bounded size so the last element doesn't read past the + * bitmap allocation when BITS_TO_LONGS(size) is odd. + */ for (i =3D 0; (u64) i < BITS_TO_U64(size); i++) { - ret =3D do_write(ff, p + i, sizeof(*p)); + u64 val =3D 0; + size_t off =3D i * sizeof(val); + + memcpy(&val, (char *)set + off, min(sizeof(val), byte_size - off)); + ret =3D do_write(ff, &val, sizeof(val)); if (ret < 0) return ret; } @@ -332,7 +342,18 @@ static int do_read_bitmap(struct feat_fd *ff, unsigned= long **pset, u64 *psize) if (ret) return ret; =20 - set =3D bitmap_zalloc(size); + /* Bitmap APIs use int for nbits; reject u64 values that truncate. */ + if (size > INT_MAX || + BITS_TO_U64(size) > (ff->size - ff->offset) / sizeof(u64)) + return -1; + + /* + * bitmap_zalloc() allocates in unsigned long units, which are only + * 4 bytes on 32-bit architectures. The read loop below casts the + * buffer to u64 * and writes 8-byte elements, so allocate in u64 + * units to ensure the buffer is large enough. + */ + set =3D calloc(BITS_TO_U64(size), sizeof(u64)); if (!set) return -ENOMEM; =20 @@ -3488,7 +3509,7 @@ static int process_mem_topology(struct feat_fd *ff, return -1; } =20 - if (ff->size < 3 * sizeof(u64) + nr * 2 * sizeof(u64)) { + if (ff->size < 3 * sizeof(u64) + nr * 3 * sizeof(u64)) { pr_err("Invalid HEADER_MEM_TOPOLOGY: section too small (%zu) for %llu no= des\n", ff->size, (unsigned long long)nr); return -1; @@ -3523,7 +3544,7 @@ static int process_mem_topology(struct feat_fd *ff, =20 out: if (ret) - free(nodes); + memory_node__delete_nodes(nodes, nr); return ret; } =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBF1D270EDF; Sun, 10 May 2026 03:36:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384200; cv=none; b=R3yJm59JDRSdMxCPQw1T2rmLj6jcSOsIQH6unHwSmSwT+qjCfzCb4ihVZRCNfS3J0jU2YH5G5UdI9gdrKlhioQ7qso0D5lV7WmWc8xA/9rlXF0q9J1sEVR7M/x2MqErpzM3WWbwYkTLTJLlIYRGW1wJLzxNEP33fmRsRUdS0O2I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384200; c=relaxed/simple; bh=VcfiWO5Vp0JgEdvLsStB4zHN67Q0J7XbmZEFgWFlUPE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=llahEqFdsUBU2cE+2YR693CCRyh8RvWDXJIOcCzahJ3wx6bZBYVWnV1FuUCaV5mQDXZx1tSZvVJ7dM/Xiw/qLqG2O7IarvmTMGM6cz+R0hUFc/9zh8xU8GtMcbgqtJ4KvBT4gbuPfdqUtPxax/gMidBMOVRe/uoJzU31Ag/0D74= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=JRn5G6JF; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="JRn5G6JF" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2B5C5C2BCF7; Sun, 10 May 2026 03:36:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384200; bh=VcfiWO5Vp0JgEdvLsStB4zHN67Q0J7XbmZEFgWFlUPE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JRn5G6JF8jzmA2paiq/PXi0XMYpV92Hcv9sFU8BoNLiyWKghWXwcH/TFIR3I4ddoW MBeMu1+c6rZXKX37E0tnxPcr80P/mR5m1DSwevS9bV++j/xU5Cdafu/vUAf+NBys/1 TWJZlPCXgVgc1E5rjZF3WFYTioWpUhZr8we7/gh0X2GJb4tjOTTxRSrsz1/B+8O9Tq z5WFXVWIW2VPs9nKP3aqrifKmicKHUMD3Vyt+qcVq9Okbu6fkz8kegYYO7JwNS0Un3 yKo/jo7D5l8m+5HuCrZbiFQTZbqrqcvvXbgYhNzTnW+3EDE7P7lRyLTW9U5jq3qWII EcL9TnN1UweFw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, Chun-Tse Shao , "Claude Opus 4.6 (1M context)" Subject: [PATCH 22/28] perf session: Add byte-swap for PERF_RECORD_COMPRESSED2 events Date: Sun, 10 May 2026 00:34:13 -0300 Message-ID: <20260510033424.255812-23-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo PERF_RECORD_COMPRESSED2 has a data_size field (__u64) at offset 8 that is not covered by the header byte-swap in prefetch_event(). When reading a cross-endian perf.data file, data_size was used without swapping, causing either garbage decompression sizes or silent data corruption. PERF_RECORD_COMPRESSED (the original format) has no fields beyond the header, so it doesn't need a swap op. Add perf_event__compressed2_swap() and register it in perf_event__swap_ops[]. Fixes: 208c0e168344 ("perf record: Add 8-byte aligned event type PERF_RECOR= D_COMPRESSED2") Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Chun-Tse Shao Cc: Ian Rogers Cc: Jiri Olsa Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/session.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 85591ccdc2e8ada3..80cb03d150cecc0b 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1037,6 +1037,14 @@ static int perf_event__time_conv_swap(union perf_eve= nt *event, return 0; } =20 +static int perf_event__compressed2_swap(union perf_event *event, + bool sample_id_all __maybe_unused) +{ + /* Only data_size needs swapping =E2=80=94 compressed payload is a raw by= te stream */ + event->pack2.data_size =3D bswap_64(event->pack2.data_size); + return 0; +} + static int perf_event__bpf_metadata_swap(union perf_event *event, bool sample_id_all __maybe_unused) { @@ -1175,6 +1183,7 @@ static perf_event__swap_op perf_event__swap_ops[] =3D= { [PERF_RECORD_STAT_ROUND] =3D perf_event__stat_round_swap, [PERF_RECORD_EVENT_UPDATE] =3D perf_event__event_update_swap, [PERF_RECORD_TIME_CONV] =3D perf_event__time_conv_swap, + [PERF_RECORD_COMPRESSED2] =3D perf_event__compressed2_swap, [PERF_RECORD_BPF_METADATA] =3D perf_event__bpf_metadata_swap, [PERF_RECORD_SCHEDSTAT_CPU] =3D perf_event__schedstat_cpu_swap, [PERF_RECORD_SCHEDSTAT_DOMAIN] =3D perf_event__schedstat_domain_swap, --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C121A19CD0A; Sun, 10 May 2026 03:36:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384204; cv=none; b=jLhGkrdW+vPz105nXKRvIHIgcDKyzqJAhYD14LutSd8/4ypgGsIbPhKwMhP4YqrQCYYBdqUZnbInHce0oRMUETqKi2m8s8ZSGE+TxQSG06ZsSbmMvPR2fKFImJN7lNleGISaG/sGVc5Sd0Wq6s1JpJ5i+thHEDp1IIz8YBDxdw0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384204; c=relaxed/simple; bh=7eJTKkRkPEdMSMjklTcR1Ee4Xwpm0KUX1Tfr4R7f9Cs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=lwyHr03oeMEIp1gSe66XF5+t2ze+1FByKB5QpGl/D5/ZlL4B2KuRs4bKJBB8k8KhDm2giuyZ34AHh7LJelGHq/0mZWu5K01mLZyrcsypLkKQomqwB0peuNtc6oC4AjKFEPELCIutU3IJ2Sabep0P8fJ4NXxgexJIxGvYunsbiSs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=vNT13Vju; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="vNT13Vju" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0F35CC2BCB8; Sun, 10 May 2026 03:36:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384204; bh=7eJTKkRkPEdMSMjklTcR1Ee4Xwpm0KUX1Tfr4R7f9Cs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vNT13VjuHKT5QcGunpuBo/NSy1IRgCoxKIFAk4RQllVjbTI+iQDXEMi4B6UKXXrLC 4oLkdCHPq5UsHoiF9AJNnynYHDsUGfoP5BsjBWhKm2LKXAdiaemAJIViO4dPyYbG77 2Fn/0TrY6eW2AbP8NZBaYZw85n+u99dVgTrR6H74vZuB1nvMI+WfJCZstK37smsnpH eQW9V65JjQKsMBg1EYjScBq1FCrkgO9/rIiqN7SllxTHbHVJgSvERB1ihHcll83P2N mjIDeLCScvCNxpw5yxl941Sek3PSjk1jb8UWr++c6X9+D81CB0rvoRzBSJfYtVVBru 9AZogGH3pbJGQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 23/28] perf tools: Harden compressed event processing Date: Sun, 10 May 2026 00:34:14 -0300 Message-ID: <20260510033424.255812-24-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Add several hardening checks to the compressed event decompression pipeline: 1. Guard against decomp_last_rem underflow: check that decomp_last->head does not exceed decomp_last->size before subtracting. A u64 underflow here would produce a huge decomp_len, causing an oversized mmap allocation. 2. Validate comp_mmap_len from the HEADER_COMPRESSED feature section: reject values that are not 4K-aligned, smaller than 4096, or larger than ~2 GB (prevents size_t overflow when adding decomp_last_rem on 32-bit, while allowing legitimate large mmap buffers from perf record -m). 3. Validate COMPRESSED event header size: reject events where header.size is too small to contain the fixed struct fields, preventing underflow in the payload size calculation. 4. Validate COMPRESSED2 event data_size: check that data_size does not exceed the available payload (header.size minus the fixed struct fields) for the newer compressed format. 5. Reject compressed events when the HEADER_COMPRESSED feature is missing from the file header, which means no decompression context was initialized. Reported-by: sashiko-bot@kernel.org # Running on a local machine Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/header.c | 17 +++++++++++++++++ tools/perf/util/tool.c | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index bda8705e87648800..994e54167ea3196b 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -3849,6 +3849,23 @@ static int process_compressed(struct feat_fd *ff, if (do_read_u32(ff, &(env->comp_mmap_len))) return -1; =20 + /* + * FIXME: perf.data should record the recording system's page + * size =E2=80=94 it affects mmap buffer alignment, sample addresses, + * and data_page_size/code_page_size interpretation. Without + * it we assume 4K (the smallest Linux page size) as a safe + * minimum alignment for comp_mmap_len validation. + * + * Cap at 2 GB to keep decomp_len + decomp_last_rem + + * sizeof(struct decomp) within size_t range on 32-bit. + */ + if (env->comp_mmap_len < 4096 || env->comp_mmap_len % 4096 || + env->comp_mmap_len > (2U * 1024 * 1024 * 1024 - 4096)) { + pr_err("Invalid HEADER_COMPRESSED: comp_mmap_len (%u) must be a 4K-align= ed value in [4096, %u]\n", + env->comp_mmap_len, 2U * 1024 * 1024 * 1024 - 4096); + return -1; + } + return 0; } =20 diff --git a/tools/perf/util/tool.c b/tools/perf/util/tool.c index ff2150517b75587a..96fa6d6c55cdca1e 100644 --- a/tools/perf/util/tool.c +++ b/tools/perf/util/tool.c @@ -24,7 +24,15 @@ static int perf_session__process_compressed_event(const = struct perf_tool *tool _ size_t mmap_len, decomp_len =3D perf_session__env(session)->comp_mmap_len; struct decomp *decomp, *decomp_last =3D session->active_decomp->decomp_la= st; =20 + if (!decomp_len) { + pr_err("Compressed events found but HEADER_COMPRESSED not set\n"); + return -1; + } + if (decomp_last) { + /* Prevent u64 underflow in decomp_last_rem */ + if (decomp_last->head > decomp_last->size) + return -1; decomp_last_rem =3D decomp_last->size - decomp_last->head; decomp_len +=3D decomp_last_rem; } @@ -47,14 +55,37 @@ static int perf_session__process_compressed_event(const= struct perf_tool *tool _ decomp->size =3D decomp_last_rem; } =20 + /* + * Events are read directly from the mmap'd file; fields could + * theoretically change via a FUSE-backed file, but that applies + * to the entire event processing pipeline, not just here. + */ if (event->header.type =3D=3D PERF_RECORD_COMPRESSED) { + if (event->header.size < sizeof(struct perf_record_compressed)) + goto err_decomp; src =3D (void *)event + sizeof(struct perf_record_compressed); src_size =3D event->pack.header.size - sizeof(struct perf_record_compres= sed); } else if (event->header.type =3D=3D PERF_RECORD_COMPRESSED2) { + /* + * prefetch_event() only guarantees that the 8-byte + * event header fits; validate that header.size covers + * the data_size field before accessing it, otherwise a + * crafted event reads data_size from adjacent memory. + */ + if (event->header.size < sizeof(struct perf_record_compressed2)) + goto err_decomp; src =3D (void *)event + sizeof(struct perf_record_compressed2); src_size =3D event->pack2.data_size; + /* + * data_size is independent of header.size (which + * includes padding); verify it doesn't exceed the + * actual payload to prevent out-of-bounds reads in + * zstd_decompress_stream(). + */ + if (src_size > event->header.size - sizeof(struct perf_record_compressed= 2)) + goto err_decomp; } else { - return -1; + goto err_decomp; } =20 decomp_size =3D zstd_decompress_stream(session->active_decomp->zstd_decom= p, src, src_size, @@ -77,6 +108,11 @@ static int perf_session__process_compressed_event(const= struct perf_tool *tool _ pr_debug("decomp (B): %zd to %zd\n", src_size, decomp_size); =20 return 0; + +err_decomp: + munmap(decomp, mmap_len); + pr_err("Couldn't decompress data\n"); + return -1; } #endif =20 --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A6CA1285CB4; Sun, 10 May 2026 03:36:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384209; cv=none; b=rbgV/Wl+rrlh7YPnHTtfVSd/ENHmDGSQSYwstav8l7SRwdTrl8elBSt4S1y/FoYREYKIh5kyIDaspgXEsA9kXkFAp0a96NNldS5kNWHxHD1IFN7+4WAqqH2X93oNshdwop5P/H2Zy7uRLsAGGwowmzrmH+WFT6ZXQvNfxhpW34I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384209; c=relaxed/simple; bh=BmgSgr1QoQ1SSGA4sPYjM5DUkysC6eOki83batJ4ME4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OYllW7TTRPyz+F5IoJrOxdnflrSDlsPg1gKh3FYD2OtDr83lrQwDSgaHbzOhWJVpSuOoqKgcdUS2D3rXg7I3K1L6jNVLrGtPonmTUXVvZTnOCzLE/ph2otbKjKbYVTjhLdkw78JUKQ0A6PAYx+P+qnJrwgfixejAyDxBu0RgDV4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=L4uFi53a; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="L4uFi53a" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2D68BC2BCF6; Sun, 10 May 2026 03:36:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384209; bh=BmgSgr1QoQ1SSGA4sPYjM5DUkysC6eOki83batJ4ME4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=L4uFi53aRs03Kf/xtylfUfK4MT3TTrgpgjcspSSQxewWJSlhm2jMqKn9LSD+YgH8v ds7FQZjXjW3Rg/TMasp6uq8/sbty8NhkwTL84iCQMy0oJ6wWYZxFjDRNaDt+rhi1z5 /lC5Ynp+c1SD09/hEJZ7h2mv/P+KvWrGpEBogV9iY0MjAyXQ516Sjv+8AfycL3NWog t2K68QPW+mP3mVpa6EiZw60STbpOon/IcpOIIwtLr+D5eZAadwd2EZTyqi7+vsaM7b A+g0DRfhBT3Ss/EFST74TFsRxp35lRqzpUlUigPtgVn5tUxqsDlmyq8UV8Q1hoUHYR H2jdHc61tMnNA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 24/28] perf session: Check for decompression buffer size overflow Date: Sun, 10 May 2026 00:34:15 -0300 Message-ID: <20260510033424.255812-25-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo On 32-bit systems, sizeof(struct decomp) + decomp_len can wrap size_t when comp_mmap_len is large. The preceding patch caps comp_mmap_len at ~2 GB, which prevents decomp_len from exceeding SIZE_MAX after adding decomp_last_rem, but the subsequent addition of sizeof(struct decomp) could still theoretically overflow on systems with very small size_t, resulting in a tiny mmap allocation while zstd receives the original large decomp_len as the destination size. Add an explicit overflow check before computing mmap_len as defense-in-depth. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Jiri Olsa Cc: Ian Rogers Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/tool.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/perf/util/tool.c b/tools/perf/util/tool.c index 96fa6d6c55cdca1e..bb13d526ce4c3382 100644 --- a/tools/perf/util/tool.c +++ b/tools/perf/util/tool.c @@ -34,9 +34,22 @@ static int perf_session__process_compressed_event(const = struct perf_tool *tool _ if (decomp_last->head > decomp_last->size) return -1; decomp_last_rem =3D decomp_last->size - decomp_last->head; + /* + * Check before adding: on 32-bit, size_t +=3D u64 + * silently truncates, bypassing the overflow check + * below and producing an undersized buffer. + */ + if (decomp_last_rem > SIZE_MAX - decomp_len - sizeof(struct decomp)) { + pr_err("Decompression buffer size overflow\n"); + return -1; + } decomp_len +=3D decomp_last_rem; } =20 + if (decomp_len > SIZE_MAX - sizeof(struct decomp)) { + pr_err("Decompression buffer size overflow\n"); + return -1; + } mmap_len =3D sizeof(struct decomp) + decomp_len; decomp =3D mmap(NULL, mmap_len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DE703285CB6; Sun, 10 May 2026 03:36:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384214; cv=none; b=h8W9d7C7VhKNLeXCU4IBsCQw8FE1xro5Z/ZIeTlMsWK4W9gwNc59pPu6d9ZbLjgTtqofOPASGtFoRig0ddNZfm2yFE2qOtzfzmdP5cdp7sByYDWHPmlmkbiPxCISSp2fh+NRJzJ0y7+oOaHqsEHKAnA6jw0eiL9DTUqbXW+v0Ug= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384214; c=relaxed/simple; bh=fdT6ZtqV6FXdsmthN+Sha6Rf+AOARgwXiDiEj+FPl4I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=si5F8h6/Ku/Zx5Ju1piCcLDGhGFsj+xhHBfyySbjVgFi5Lnyl1NP2gR7oeQC3NePuPhaaiCSVRW3mO6J7QK2jXXkj7r52QqXvcEDZs5p1xT88L9Mb2AfGa4gKoZQpNxaqN+sHmLnFt80+52pNB8ocQx6kojlq4Hyf9KuPd+nDfc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=A1ncqYPx; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="A1ncqYPx" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3832FC2BCF6; Sun, 10 May 2026 03:36:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384214; bh=fdT6ZtqV6FXdsmthN+Sha6Rf+AOARgwXiDiEj+FPl4I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=A1ncqYPx7cKdAmN+0JtqY4hGJLMBRo3FUDFccwdISbix2bhgkwhU+/0YKej4ciujD Xf1pnFmfmMnuAHADnmmZYkaqtpLuEX3Wt/mw/ePgb36/fU9eNyo6Tv71TSkGYovYEq 5FH/lW81nnOuRf9tvWaeQrXCARtF70wxyNN9XvmueEYJY9Ujct6yK+P+8UjT0bw9F3 fHTgwHU1UUVHxa4LoYc1auUSd5vpwHCWjb/oNMAu6Uz0bPPsbOdshE3dSj8gRkbxSl YUHrbNmVKVpaaOH+Sjfp9zdH29cU6F/ly5JymZuKKhfoVbYPF2f6WclcSglUPnbPCP C78y02rv34nFA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 25/28] perf session: Bound nr_cpus_avail and validate sample CPU Date: Sun, 10 May 2026 00:34:16 -0300 Message-ID: <20260510033424.255812-26-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Several downstream consumers (timechart, kwork, sched) use fixed-size arrays indexed by CPU. A crafted perf.data can supply arbitrary CPU values that index past these arrays, causing out-of-bounds access. Clamp nr_cpus_avail to MAX_NR_CPUS when reading HEADER_NRCPUS, and fall back to MAX_NR_CPUS when the header is missing (truncated files, pipe mode, pre-2017 perf). Then validate sample.cpu against nr_cpus_avail in perf_session__deliver_event() before any tool callback runs. Only validate when PERF_SAMPLE_CPU is set in sample_type =E2=80=94 when absent, evsel__parse_sample() leaves sample.cpu as (u32)-1, a sentinel that downstream tools (script, inject) check to identify events without CPU info. Clamping it to 0 would break those checks. Also refactor the sample parsing in perf_session__deliver_event() to call evsel__parse_sample() directly (via evlist__event2evsel() for evsel lookup), with explicit guest VM SID resolution for machine_pid and vcpu fields. Fix an off-by-one in end_sample_processing(): change the loop bound from cpu <=3D numcpus to cpu < numcpus to prevent accessing one element past the array. For pipe-mode streams where HEADER_NRCPUS may arrive late or not at all, the MAX_NR_CPUS fallback ensures the bounds check is still effective against the fixed-size downstream arrays. Reported-by: sashiko-bot@kernel.org # Running on a local machine Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/builtin-timechart.c | 2 +- tools/perf/util/header.c | 43 +++++++++++++++++++ tools/perf/util/session.c | 75 +++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 28f33e39895d362d..40297f2dcd0353cc 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -700,7 +700,7 @@ static void end_sample_processing(struct timechart *tch= art) u64 cpu; struct power_event *pwr; =20 - for (cpu =3D 0; cpu <=3D tchart->numcpus; cpu++) { + for (cpu =3D 0; cpu < tchart->numcpus; cpu++) { /* C state */ #if 0 pwr =3D zalloc(sizeof(*pwr)); diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 994e54167ea3196b..30b65c58784b596f 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -48,6 +48,7 @@ #include #include "asm/bug.h" #include "tool.h" +#include "../perf.h" #include "time-utils.h" #include "units.h" #include "util/util.h" // perf_exe() @@ -2884,12 +2885,36 @@ static int process_nrcpus(struct feat_fd *ff, void = *data __maybe_unused) if (ret) return ret; =20 + /* Validate raw values before clamping */ if (nr_cpus_online > nr_cpus_avail) { pr_err("Invalid HEADER_NRCPUS: nr_cpus_online (%u) > nr_cpus_avail (%u)\= n", nr_cpus_online, nr_cpus_avail); return -1; } =20 + /* + * FIXME: Several downstream consumers use fixed-size arrays + * indexed by CPU (timechart MAX_CPUS, kwork/sched/annotate + * DECLARE_BITMAP(MAX_NR_CPUS)). Until these are converted + * to dynamic allocation, clamp nr_cpus_avail so per-event + * CPU bounds checks reject samples above the array limit. + * Data from CPUs beyond MAX_NR_CPUS will be lost. + * + * Pipe-mode streams from pre-2017 perf or third-party tools + * that lack HEADER_NRCPUS will hit the MAX_NR_CPUS fallback + * in perf_session__deliver_event() instead. + */ + if (nr_cpus_avail > MAX_NR_CPUS) { + pr_warning("WARNING: perf.data recorded on a %u-CPU machine but perf is = compiled with MAX_NR_CPUS=3D%d.\n" + " Samples from CPUs >=3D %d will be clamped to CPU 0. Consi= der rebuilding\n" + " perf with a larger MAX_NR_CPUS, or help convert fixed-size= CPU arrays to\n" + " dynamic allocation.\n", + nr_cpus_avail, MAX_NR_CPUS, MAX_NR_CPUS); + nr_cpus_avail =3D MAX_NR_CPUS; + if (nr_cpus_online > nr_cpus_avail) + nr_cpus_online =3D nr_cpus_avail; + } + env->nr_cpus_avail =3D (int)nr_cpus_avail; env->nr_cpus_online =3D (int)nr_cpus_online; return 0; @@ -5239,6 +5264,24 @@ int perf_session__read_header(struct perf_session *s= ession) #endif } =20 + /* + * Without nr_cpus_avail the sample CPU bounds check in + * perf_session__deliver_event() is bypassed, allowing crafted + * CPU IDs to reach downstream consumers that index fixed-size + * arrays (timechart, kwork, sched =E2=80=94 all sized MAX_NR_CPUS). + * + * This can happen with truncated files (interrupted recording + * loses all feature sections), very old files that predate + * HEADER_NRCPUS, or crafted files that omit it. Fall back to + * MAX_NR_CPUS so the bounds check is still effective =E2=80=94 any + * CPU ID below that limit is safe for all downstream arrays. + */ + if (header->env.nr_cpus_avail =3D=3D 0) { + header->env.nr_cpus_avail =3D MAX_NR_CPUS; + pr_warning("WARNING: perf.data is missing HEADER_NRCPUS, using MAX_NR_CP= US (%d) as CPU bound\n", + MAX_NR_CPUS); + } + return 0; out_errno: return -errno; diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 80cb03d150cecc0b..dd84b3cd017a5073 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -2085,14 +2085,87 @@ static int perf_session__deliver_event(struct perf_= session *session, const char *file_path) { struct perf_sample sample; + struct evsel *evsel; int ret; =20 perf_sample__init(&sample, /*all=3D*/false); - ret =3D evlist__parse_sample(session->evlist, event, &sample); + evsel =3D evlist__event2evsel(session->evlist, event); + if (!evsel) { + ret =3D -EFAULT; + goto out; + } + ret =3D evsel__parse_sample(evsel, event, &sample); if (ret) { pr_err("Can't parse sample, err =3D %d\n", ret); goto out; } + /* + * evsel__parse_sample() doesn't populate machine_pid/vcpu, + * which are needed by machines__find_for_cpumode() to + * attribute samples to guest VMs. The SID table maps + * sample IDs to the guest that owns the event. + */ + if (perf_guest && sample.id) { + struct perf_sample_id *sid =3D evlist__id2sid(session->evlist, sample.id= ); + + if (sid) { + sample.machine_pid =3D sid->machine_pid; + sample.vcpu =3D sid->vcpu.cpu; + } + } + + /* + * Validate sample.cpu before any callback can use it as an + * array index (kwork cpus_runtime, timechart cpus_cstate_*, + * sched cpu_last_switched). + * + * When PERF_SAMPLE_CPU is absent, evsel__parse_sample() leaves + * sample.cpu as (u32)-1 =E2=80=94 a sentinel that downstream tools + * (script, inject) check to identify events without CPU info. + * Only check when sample.cpu was actually populated from event + * data: PERF_RECORD_SAMPLE always has it when PERF_SAMPLE_CPU + * is set; non-sample events only have it when sample_id_all is + * enabled. Otherwise sample.cpu is the (u32)-1 sentinel from + * evsel__parse_sample() and must not be validated or clamped. + */ + if ((evsel->core.attr.sample_type & PERF_SAMPLE_CPU) && + (event->header.type =3D=3D PERF_RECORD_SAMPLE || + evsel->core.attr.sample_id_all)) { + int nr_cpus_avail =3D perf_session__env(session)->nr_cpus_avail; + + /* + * For perf.data files the MAX_NR_CPUS fallback in + * perf_session__read_header() guarantees this is set. + * For pipe mode, HEADER_NRCPUS may arrive late or not + * at all (pre-2017 perf, third-party tools). Fall + * back to MAX_NR_CPUS so the bounds check still works + * against fixed-size downstream arrays. + */ + if (nr_cpus_avail <=3D 0) { + nr_cpus_avail =3D MAX_NR_CPUS; + perf_session__env(session)->nr_cpus_avail =3D nr_cpus_avail; + pr_warning_once("WARNING: HEADER_NRCPUS not set, using MAX_NR_CPUS (%d)= as CPU bound\n", + MAX_NR_CPUS); + } + if (sample.cpu >=3D (u32)nr_cpus_avail && + sample.cpu !=3D (u32)-1) { + /* + * Warn rather than abort: synthesized events + * (MMAP, COMM) lack sample_id_all data, so + * parse_id_sample reads garbage from the event + * payload. Clamping to 0 protects downstream + * array indexing while keeping the session alive. + * + * Preserve (u32)-1: perf script and perf inject + * use it as a sentinel for "CPU not applicable." + * Downstream array users (timechart, kwork) have + * their own per-callback bounds checks. + */ + pr_warning_once("WARNING: sample CPU %u >=3D nr_cpus_avail %u, clamping= to 0\n", + sample.cpu, nr_cpus_avail); + sample.cpu =3D 0; + } + } =20 ret =3D auxtrace__process_event(session, event, &sample, tool); if (ret < 0) --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0DAB6284682; Sun, 10 May 2026 03:37:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384221; cv=none; b=oLzW7ysf/ZRCoNX6iwFxZbq5iQB/nFWsRztApTVRysIX5rU7nXJ3Dow21XGQeiwr0BUMpFBV/f/e+A/dqHTCnF2p1ylZqfLgDVVEdTpVsJy4gZSlGgqYujoy3+3QGfptO8TNNR8ANk7nkuPjT+vSZUm2sXP5LirqJ1OZ7JTbjXk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384221; c=relaxed/simple; bh=OxiYtueaUwtD6LMPKL557/3miX7XjO9SYu8JPtHC5Jw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=SOiUapwcr9fkzwUwIDAuawJgfu/TL6UjWmGC5quWMDBNJeqpYLYIXjnraLthBarjFp2bODVA7am6U6D8+X3DeMmAMEiTMixxqNaZc8M2xZDnkrLXnlkZXLbfvIpw50kh3tcphP2tfgiHXbct7Da9ZNL6f0qJXdxDT6+elq4Clhc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=odGO7vBu; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="odGO7vBu" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6E692C2BCF6; Sun, 10 May 2026 03:36:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384220; bh=OxiYtueaUwtD6LMPKL557/3miX7XjO9SYu8JPtHC5Jw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=odGO7vBu+IH8+h3VYVVMza6a9BYrZ7Ao4XSGsPB980d0gubN2ClWSpKa8vXyuUugc CtwXlAypXTbpqM8Qk89g4y0WlcH5ohRCV+poP51lkRvSF4uehyP3rKIiTgzatcWxeY uAgAKxgeO5KVzG5DOdpdUju+iKEIZfYmcEuL2h992XxP87mqbwk5aIxpAomYIu/usG LNzgYLn8DsZjqW+9amLRAPIsUDl93tywdxqRSKfDqa/yA3YIDhjFKAVT4ss1f7rkUz Nslj4dGemUbyx9EAnwaVBfrvxw/QwUjf+1c5jihDQ3FB4Mm6Z2Z6x5s0mk7/9I1WYj cDDxkVgULsB8Q== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, "Claude Opus 4.6 (1M context)" Subject: [PATCH 26/28] perf timechart: Bounds check cpu_id and fix topology_map allocation Date: Sun, 10 May 2026 00:34:17 -0300 Message-ID: <20260510033424.255812-27-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo The cpu_idle, cpu_frequency, power_start, and power_frequency tracepoint handlers extract cpu_id from the event payload via evsel__intval() and use it directly as an array index into cpus_cstate_start_times[] and cpus_pstate_start_times[], which are allocated with MAX_CPUS (4096) entries. Unlike sample->cpu which is validated in perf_session__deliver_event(), cpu_id comes from the tracepoint data and is never bounds checked. A crafted perf.data with a malicious cpu_id in a tracepoint event causes out-of-bounds array accesses. Validate cpu_id against tchart->numcpus (nr_cpus_avail from the file header) and reject the event with an error if it is out of range, as this indicates a corrupted or crafted file. The power_end handler uses sample->cpu (not a tracepoint cpu_id field). Add a bounds check there too since a crafted file could omit PERF_SAMPLE_CPU, leaving sample->cpu as the (u32)-1 sentinel which would cause out-of-bounds access in c_state_end(). Also validate sample->cpu in sched_switch and sched_wakeup handlers, which store it in cpu_sample structs later used as array indices into topology_map[] during SVG generation. Fix svg_build_topology_map() to allocate topology_map using nr_cpus_avail instead of nr_cpus_online. When offline CPUs exist, nr_cpus_online < nr_cpus_avail, and a valid cpu_id that passes the numcpus check could still exceed the topology_map allocation, causing a heap out-of-bounds read in cpu2y(). Reject negative CPU values in str_to_bitmap() to prevent perf_cpu_map__new("") on an empty topology string from passing -1 to __set_bit(), which would write at offset ULONG_MAX/BITS_PER_LONG. Fix the pre-existing backtrace memory leak: change the tracepoint_handler typedef to pass const char **backtrace (pointer-to-pointer). Handlers that consume the string (sched_switch, sched_wakeup) set *backtrace =3D NULL to claim ownership. The caller always calls free() after the handler returns =E2=80=94 if ownership was taken the pointer is NULL and free(NULL) is a no-op. Skip cat_backtrace() entirely when tchart->with_backtrace is not set. Cap tchart->numcpus at MAX_CPUS in the HEADER_NRCPUS callback so the bounds check cannot exceed the array allocation size. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Ian Rogers Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/builtin-timechart.c | 115 ++++++++++++++++++++++++++++----- tools/perf/util/svghelper.c | 6 +- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 40297f2dcd0353cc..bccc48cfb99a1d57 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -71,6 +71,7 @@ struct timechart { bool io_only, skip_eagain; u64 io_events; + u32 nr_invalid_cpu; u64 min_time, merge_dist; }; @@ -569,7 +570,7 @@ static const char *cat_backtrace(union perf_event *even= t, typedef int (*tracepoint_handler)(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace); + const char **backtrace); =20 static int process_sample_event(const struct perf_tool *tool, union perf_event *event, @@ -588,22 +589,46 @@ static int process_sample_event(const struct perf_too= l *tool, =20 if (evsel->handler !=3D NULL) { tracepoint_handler f =3D evsel->handler; - return f(tchart, evsel, sample, - cat_backtrace(event, sample, machine)); + const char *bt =3D NULL; + int ret; + + if (tchart->with_backtrace) + bt =3D cat_backtrace(event, sample, machine); + ret =3D f(tchart, evsel, sample, &bt); + /* + * Handlers that consume backtrace (sched_switch, + * sched_wakeup) store the pointer and set *bt =3D NULL + * to claim ownership. For all other handlers bt is + * still ours to free. free(NULL) is safe. + */ + free((void *)bt); + return ret; } =20 return 0; } =20 static int -process_sample_cpu_idle(struct timechart *tchart __maybe_unused, +process_sample_cpu_idle(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace __maybe_unused) + const char **backtrace __maybe_unused) { u32 state =3D evsel__intval(evsel, sample, "state"); u32 cpu_id =3D evsel__intval(evsel, sample, "cpu_id"); =20 + /* + * cpu_id from tracepoint data indexes cpus_cstate_start_times[] + * and cpus_pstate_start_times[], both allocated as MAX_CPUS + * entries. Reject out-of-range values to prevent OOB writes; + * numcpus (from nr_cpus_avail) is the tighter, valid bound. + */ + if (cpu_id >=3D tchart->numcpus) { + pr_err("cpu_idle event cpu_id %u >=3D nr_cpus_avail %u\n", + cpu_id, tchart->numcpus); + return -EINVAL; + } + if (state =3D=3D (u32)PWR_EVENT_EXIT) c_state_end(tchart, cpu_id, sample->time); else @@ -615,11 +640,18 @@ static int process_sample_cpu_frequency(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace __maybe_unused) + const char **backtrace __maybe_unused) { u32 state =3D evsel__intval(evsel, sample, "state"); u32 cpu_id =3D evsel__intval(evsel, sample, "cpu_id"); =20 + /* Same bounds check as process_sample_cpu_idle =E2=80=94 see comment the= re */ + if (cpu_id >=3D tchart->numcpus) { + pr_err("cpu_frequency event cpu_id %u >=3D nr_cpus_avail %u\n", + cpu_id, tchart->numcpus); + return -EINVAL; + } + p_state_change(tchart, cpu_id, sample->time, state); return 0; } @@ -628,13 +660,20 @@ static int process_sample_sched_wakeup(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace) + const char **backtrace) { u8 flags =3D evsel__intval(evsel, sample, "common_flags"); int waker =3D evsel__intval(evsel, sample, "common_pid"); int wakee =3D evsel__intval(evsel, sample, "pid"); =20 - sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, back= trace); + /* sample->cpu used as index into topology_map[] during SVG generation */ + if (sample->cpu >=3D tchart->numcpus) { + tchart->nr_invalid_cpu++; + return 0; + } + + sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, *bac= ktrace); + *backtrace =3D NULL; return 0; } =20 @@ -642,27 +681,41 @@ static int process_sample_sched_switch(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace) + const char **backtrace) { int prev_pid =3D evsel__intval(evsel, sample, "prev_pid"); int next_pid =3D evsel__intval(evsel, sample, "next_pid"); u64 prev_state =3D evsel__intval(evsel, sample, "prev_state"); =20 + /* sample->cpu used as index into topology_map[] during SVG generation */ + if (sample->cpu >=3D tchart->numcpus) { + tchart->nr_invalid_cpu++; + return 0; + } + sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid, - prev_state, backtrace); + prev_state, *backtrace); + *backtrace =3D NULL; return 0; } =20 #ifdef SUPPORT_OLD_POWER_EVENTS static int -process_sample_power_start(struct timechart *tchart __maybe_unused, +process_sample_power_start(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace __maybe_unused) + const char **backtrace __maybe_unused) { u64 cpu_id =3D evsel__intval(evsel, sample, "cpu_id"); u64 value =3D evsel__intval(evsel, sample, "value"); =20 + /* Same bounds check as process_sample_cpu_idle =E2=80=94 see comment the= re */ + if (cpu_id >=3D tchart->numcpus) { + pr_err("power_start event cpu_id %" PRIu64 " >=3D nr_cpus_avail %u\n", + cpu_id, tchart->numcpus); + return -EINVAL; + } + c_state_start(cpu_id, sample->time, value); return 0; } @@ -671,8 +724,16 @@ static int process_sample_power_end(struct timechart *tchart, struct evsel *evsel __maybe_unused, struct perf_sample *sample, - const char *backtrace __maybe_unused) + const char **backtrace __maybe_unused) { + /* + * sample->cpu is validated centrally when PERF_SAMPLE_CPU is + * set, but a crafted file could omit it from sample_type. + */ + if (sample->cpu >=3D tchart->numcpus) { + tchart->nr_invalid_cpu++; + return 0; + } c_state_end(tchart, sample->cpu, sample->time); return 0; } @@ -681,11 +742,18 @@ static int process_sample_power_frequency(struct timechart *tchart, struct evsel *evsel, struct perf_sample *sample, - const char *backtrace __maybe_unused) + const char **backtrace __maybe_unused) { u64 cpu_id =3D evsel__intval(evsel, sample, "cpu_id"); u64 value =3D evsel__intval(evsel, sample, "value"); =20 + /* Same bounds check as process_sample_cpu_idle =E2=80=94 see comment the= re */ + if (cpu_id >=3D tchart->numcpus) { + pr_err("power_frequency event cpu_id %" PRIu64 " >=3D nr_cpus_avail %u\n= ", + cpu_id, tchart->numcpus); + return -EINVAL; + } + p_state_change(tchart, cpu_id, sample->time, value); return 0; } @@ -1519,7 +1587,8 @@ static int process_header(struct perf_file_section *s= ection __maybe_unused, =20 switch (feat) { case HEADER_NRCPUS: - tchart->numcpus =3D ph->env.nr_cpus_avail; + /* Cap at MAX_CPUS =E2=80=94 the allocation size of cpus_cstate/pstate a= rrays */ + tchart->numcpus =3D min((int)ph->env.nr_cpus_avail, MAX_CPUS); break; =20 case HEADER_CPU_TOPOLOGY: @@ -1625,6 +1694,16 @@ static int __cmd_timechart(struct timechart *tchart,= const char *output_name) tchart, process_header); =20 + /* + * Truncated files (interrupted recording) lose all feature + * sections so the HEADER_NRCPUS callback never fires, and + * pipe mode doesn't use perf_header__process_sections at all. + * Fall back to MAX_CPUS =E2=80=94 the actual allocation size of the + * cpus_cstate/pstate arrays. + */ + if (!tchart->numcpus) + tchart->numcpus =3D MAX_CPUS; + if (!perf_session__has_traces(session, "timechart record")) goto out_delete; =20 @@ -1646,6 +1725,12 @@ static int __cmd_timechart(struct timechart *tchart,= const char *output_name) =20 pr_info("Written %2.1f seconds of trace to %s.\n", (tchart->last_time - tchart->first_time) / (double)NSEC_PER_SEC, output_= name); + + if (tchart->nr_invalid_cpu) { + pr_warning("WARNING: %u events had invalid CPU values and were skipped.\= n" + " Scheduling and power state data may be incomplete.\n", + tchart->nr_invalid_cpu); + } out_delete: perf_session__delete(session); return ret; diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c index e360e7736c7ba65b..a3c7cfecc072f3e3 100644 --- a/tools/perf/util/svghelper.c +++ b/tools/perf/util/svghelper.c @@ -736,7 +736,8 @@ static int str_to_bitmap(char *s, cpumask_t *b, int nr_= cpus) return -1; =20 perf_cpu_map__for_each_cpu(cpu, idx, map) { - if (cpu.cpu >=3D nr_cpus) { + /* perf_cpu_map__new("") yields cpu=3D-1; reject to prevent __set_bit OO= B */ + if (cpu.cpu < 0 || cpu.cpu >=3D nr_cpus) { ret =3D -1; break; } @@ -756,7 +757,8 @@ int svg_build_topology_map(struct perf_env *env) char *sib_core, *sib_thr; int ret =3D -1; =20 - nr_cpus =3D min(env->nr_cpus_online, MAX_NR_CPUS); + /* Use nr_cpus_avail: offline CPUs still need slots in the topology map */ + nr_cpus =3D min(env->nr_cpus_avail, MAX_NR_CPUS); =20 t.sib_core_nr =3D env->nr_sibling_cores; t.sib_thr_nr =3D env->nr_sibling_threads; --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6D10A282F29; Sun, 10 May 2026 03:37:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384227; cv=none; b=WXEHoJabzKeJdkVfqsUFUeHzkuXzGAZnG1CV3aOrStFHAjzbCwZG+6iM317DL3cDadoHhrlAPaKLJ+TvjxLXbXFwlKhh8a7By81S98FmjBSKAtOVAVPEdmtWKmYj59IdRfVD94rA7NgUCkQ7FEO4/SqKqZNQuu4RVYOseykCG9s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384227; c=relaxed/simple; bh=fGAeaWAb2ooLx8uf/+yJehDkdNBeLyPVlm6gPOuh/2w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=d1o52BfTZGtrzTXPbe8gb+sUag/dngrkrDX04hhMOu/eL0TezHBG0T8Y3tXUIJn69ztFR2b1bYWqzRaQBbYljE/jWgkZCAfiUxG6SNhI2BFJCG0oQORXcvczTg05UtSxQ5LmOinRuUvHXDwYy0ji+aUissOnSwCCqnvJfL+GNys= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=kSQKLWy1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="kSQKLWy1" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 920F0C2BCF6; Sun, 10 May 2026 03:37:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384227; bh=fGAeaWAb2ooLx8uf/+yJehDkdNBeLyPVlm6gPOuh/2w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kSQKLWy1uiz1JT766aIn+zklWejhUUCWEIDQnb8SuCjQbt3fns16KTbeJ3E9CWM0W rjNrgUL8JbiFONmMgJH7nULNQqd7AwCmi4BPxLRhwvnSFKmEoPsM5BxOLY7T8wHw00 7seuJ8ZlN1o1xhbLuEboASpFqXHKTypEE7Z9n0oGS0twTMAr9Ghl3HbqJ1mC9FoeCU UioNMBQGu6tcjyVi+If7pgQM+5arsrVvOSzCtVfwm70A4m60F87UrK4on8Ng7GDFRP qn2GYly6q+K+mAbv62ybasdAPl32jpfIKWLdXjj5Q1fX0UeStgO4UDJEvyDuLpn3Bd 0vWC2TdOC2TrA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , sashiko-bot@kernel.org, Yang Jihong , "Claude Opus 4.6 (1M context)" Subject: [PATCH 27/28] perf kwork: Bounds check work->cpu before indexing cpus_runtime[] Date: Sun, 10 May 2026 00:34:18 -0300 Message-ID: <20260510033424.255812-28-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo work->cpu comes from sample->cpu which is (u32)-1 when PERF_SAMPLE_CPU is absent. Stored as int, this becomes -1 which passes the signed BUG_ON(work->cpu >=3D MAX_NR_CPUS) but causes an out-of-bounds access on cpus_runtime[-1]. Replace the BUG_ON with an unsigned bounds check that skips entries with invalid CPU values, and guard the idle and irq runtime accumulators the same way. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Yang Jihong Cc: Ian Rogers Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/builtin-kwork.c | 49 +++++++++++++++++++++++++++++++++----- tools/perf/util/kwork.h | 1 + 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/tools/perf/builtin-kwork.c b/tools/perf/builtin-kwork.c index 9d3a4c779a41e383..6e124a0f277c8294 100644 --- a/tools/perf/builtin-kwork.c +++ b/tools/perf/builtin-kwork.c @@ -424,7 +424,9 @@ static bool profile_event_match(struct perf_kwork *kwor= k, u64 time =3D sample->time; struct perf_time_interval *ptime =3D &kwork->ptime; =20 - if ((kwork->cpu_list !=3D NULL) && !test_bit(cpu, kwork->cpu_bitmap)) + /* Guard test_bit: cpu =3D=3D -1 (absent PERF_SAMPLE_CPU) would index pas= t the bitmap */ + if ((kwork->cpu_list !=3D NULL) && + ((unsigned int)cpu >=3D MAX_NR_CPUS || !test_bit(cpu, kwork->cpu_bitm= ap))) return false; =20 if (((ptime->start !=3D 0) && (ptime->start > time)) || @@ -2008,7 +2010,18 @@ static void top_calc_total_runtime(struct perf_kwork= *kwork) next =3D rb_first_cached(&class->work_root); while (next) { work =3D rb_entry(next, struct kwork_work, node); - BUG_ON(work->cpu >=3D MAX_NR_CPUS); + /* + * work->cpu comes from sample->cpu which is -1 when + * PERF_SAMPLE_CPU is absent. As int that's -1, but as + * unsigned it exceeds MAX_NR_CPUS =E2=80=94 skip to avoid OOB + * on cpus_runtime[]. + */ + /* Counted and reported in perf_kwork__top_report() */ + if ((unsigned int)work->cpu >=3D MAX_NR_CPUS) { + stat->nr_skipped_cpu++; + next =3D rb_next(next); + continue; + } stat->cpus_runtime[work->cpu].total +=3D work->total_runtime; stat->cpus_runtime[MAX_NR_CPUS].total +=3D work->total_runtime; next =3D rb_next(next); @@ -2020,7 +2033,8 @@ static void top_calc_idle_time(struct perf_kwork *kwo= rk, { struct kwork_top_stat *stat =3D &kwork->top_stat; =20 - if (work->id =3D=3D 0) { + /* See comment in top_calc_total_runtime() */ + if (work->id =3D=3D 0 && (unsigned int)work->cpu < MAX_NR_CPUS) { stat->cpus_runtime[work->cpu].idle +=3D work->total_runtime; stat->cpus_runtime[MAX_NR_CPUS].idle +=3D work->total_runtime; } @@ -2032,6 +2046,12 @@ static void top_calc_irq_runtime(struct perf_kwork *= kwork, { struct kwork_top_stat *stat =3D &kwork->top_stat; =20 + /* See comment in top_calc_total_runtime() */ + if ((unsigned int)work->cpu >=3D MAX_NR_CPUS) { + stat->nr_skipped_cpu++; + return; + } + if (type =3D=3D KWORK_CLASS_IRQ) { stat->cpus_runtime[work->cpu].irq +=3D work->total_runtime; stat->cpus_runtime[MAX_NR_CPUS].irq +=3D work->total_runtime; @@ -2084,12 +2104,21 @@ static void top_calc_cpu_usage(struct perf_kwork *k= work) if (work->total_runtime =3D=3D 0) goto next; =20 + /* See comment in top_calc_total_runtime() */ + if ((unsigned int)work->cpu >=3D MAX_NR_CPUS) { + stat->nr_skipped_cpu++; + goto next; + } + __set_bit(work->cpu, stat->all_cpus_bitmap); =20 top_subtract_irq_runtime(kwork, work); =20 - work->cpu_usage =3D work->total_runtime * 10000 / - stat->cpus_runtime[work->cpu].total; + /* Guard against division by zero if no runtime was accumulated */ + if (stat->cpus_runtime[work->cpu].total) { + work->cpu_usage =3D work->total_runtime * 10000 / + stat->cpus_runtime[work->cpu].total; + } =20 top_calc_idle_time(kwork, work); next: @@ -2102,7 +2131,8 @@ static void top_calc_load_runtime(struct perf_kwork *= kwork, { struct kwork_top_stat *stat =3D &kwork->top_stat; =20 - if (work->id !=3D 0) { + /* See comment in top_calc_total_runtime() */ + if (work->id !=3D 0 && (unsigned int)work->cpu < MAX_NR_CPUS) { stat->cpus_runtime[work->cpu].load +=3D work->total_runtime; stat->cpus_runtime[MAX_NR_CPUS].load +=3D work->total_runtime; } @@ -2170,6 +2200,13 @@ static void perf_kwork__top_report(struct perf_kwork= *kwork) next =3D rb_next(next); } =20 + if (kwork->top_stat.nr_skipped_cpu) { + printf(" Warning: %u work entries with invalid CPU were excluded from t= otals.\n" + " Task runtimes may appear inflated (IRQ time not subtracted).\n" + " Consider re-recording with PERF_SAMPLE_CPU enabled.\n", + kwork->top_stat.nr_skipped_cpu); + } + printf("\n"); } =20 diff --git a/tools/perf/util/kwork.h b/tools/perf/util/kwork.h index db00269b73f24c66..10290cd779402f9d 100644 --- a/tools/perf/util/kwork.h +++ b/tools/perf/util/kwork.h @@ -194,6 +194,7 @@ struct __top_cpus_runtime { struct kwork_top_stat { DECLARE_BITMAP(all_cpus_bitmap, MAX_NR_CPUS); struct __top_cpus_runtime *cpus_runtime; + unsigned int nr_skipped_cpu; }; =20 struct perf_kwork { --=20 2.54.0 From nobody Sat May 30 08:43:34 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C9BCF285C91; Sun, 10 May 2026 03:37:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384232; cv=none; b=JfHNFkap20Z+W36nE1vTwiPsGssisSda0sbhbYSaHBuQvZ1npAQeRNLR7sPYbBHslIz5hsDARZk5LCn4oL/00ldFvwThMO3Gk9ayqitUWZR9CcAr94v8jfPxZxaafVgiht2mqC0yNrSaiTnXoBrumogn4kfyH9feUY6Py+Or7PQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778384232; c=relaxed/simple; bh=wKm9pBbrA1i3JaTJIYZEX4eG4AJGGFGH3JN/bXXlmTQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=uirfMHo5HtmYpyMpHAlCq5ZlShomQIip8VKZAThszeiW5XPeDTa1ERIXWEaUCpD0RuMfXHxPpGwtf9nKvN7m3RxAqaxR5snum8fkOkK8IM3ZPL6jnpkk/VgD7Yod7oG8+gdSCylZRRH40MZ0k/9UUSoXioi8x6l9ulf1LFUbg18= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QQUX/zNO; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QQUX/zNO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E70E6C2BCB8; Sun, 10 May 2026 03:37:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778384232; bh=wKm9pBbrA1i3JaTJIYZEX4eG4AJGGFGH3JN/bXXlmTQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QQUX/zNOhK66zBeoTgfsuUpjfpt9hTIU+1IfmsMd99Onu9jwo2h0nlkL/r7oFrNkh 089n/psYvXV+NIqw5c8K4fuINWweMhZOC4+v0w5bLqtADMksIDOW6Ui2QQ8d0JKTXZ Zs71twjLV59ZhsPa9M20V950RE3yIORIlboPcVw9/fiqkGs7lahElau6dvNXLo/GFU Z3UNR7xDrPyGXGHEjinUkfI1h+PG4ADCgjdkQFCxt76mWDzknjv09ECcVJhB+sL2pS 73LmxlZB4UaLiabZmhOx9gpNU+yRDP3Ub2KuBx1bvnnAD0W5zVotht0rXOSnZ7Ky61 qrqj/mP0uGMaQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Kan Liang , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo , "Claude Opus 4.6 (1M context)" Subject: [PATCH 28/28] perf test: Add truncated perf.data robustness test Date: Sun, 10 May 2026 00:34:19 -0300 Message-ID: <20260510033424.255812-29-acme@kernel.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260510033424.255812-1-acme@kernel.org> References: <20260510033424.255812-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Arnaldo Carvalho de Melo Add a shell test that verifies perf report handles truncated perf.data files gracefully =E2=80=94 exiting with an error code rather than crashing with SIGSEGV or SIGABRT. The test records a simple workload, then truncates the resulting perf.data at four offsets that exercise different parsing stages: 8 bytes =E2=80=94 file header magic only 64 bytes =E2=80=94 partial file header (attr section incomplete) 256 bytes =E2=80=94 into the first events (partial event headers) 75% size =E2=80=94 mid-stream truncation (partial event data) For each truncation, perf report is run and the exit code is checked. Signal-based exits (128+signal) in the range 134-159 indicate a crash and fail the test. Non-zero exits from normal error handling are expected and acceptable. This exercises the bounds checking, minimum-size validation, and error propagation added by the preceding patches in this series. Cc: Ian Rogers Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/shell/data_validation.sh | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 tools/perf/tests/shell/data_validation.sh diff --git a/tools/perf/tests/shell/data_validation.sh b/tools/perf/tests/s= hell/data_validation.sh new file mode 100755 index 0000000000000000..649f71b6cdb93202 --- /dev/null +++ b/tools/perf/tests/shell/data_validation.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Test that perf report handles truncated perf.data gracefully +# (no crash, no segfault =E2=80=94 clean error exit). +# +# Exercises the bounds checking and minimum-size validation added +# by the perf-data-validation hardening series. + +err=3D0 + +cleanup() { + rm -f "${perfdata}" "${truncated}" + trap - EXIT TERM INT +} +trap cleanup EXIT TERM INT + +perfdata=3D$(mktemp /tmp/__perf_test.perf.data.XXXXX) +truncated=3D$(mktemp /tmp/__perf_test.perf.data.XXXXX) + +# Record a simple workload +if ! perf record -o "${perfdata}" -- perf test -w noploop 2>/dev/null; then + echo "Skip: perf record failed" + cleanup + exit 2 +fi + +file_size=3D$(stat -c %s "${perfdata}") +if [ "${file_size}" -lt 512 ]; then + echo "Skip: perf.data too small (${file_size} bytes)" + cleanup + exit 2 +fi + +# Test truncation at various offsets that exercise different +# parsing stages: +# 8 =E2=80=94 file header magic only, no attrs or data +# 64 =E2=80=94 partial file header (attr section incomplete) +# 256 =E2=80=94 into the first events (partial event headers) +# 75% =E2=80=94 mid-stream truncation (partial event data) +for cut_at in 8 64 256 $((file_size * 3 / 4)); do + if [ "${cut_at}" -ge "${file_size}" ]; then + continue + fi + head -c "${cut_at}" "${perfdata}" > "${truncated}" + + # perf report should exit with an error, not crash. + # Suppress stdout/stderr =E2=80=94 we only care about the exit code. + perf report -i "${truncated}" --stdio > /dev/null 2>&1 + exit_code=3D$? + + # 139 =3D SIGSEGV, 134 =3D SIGABRT, 136 =3D SIGFPE + if [ ${exit_code} -ge 134 ] && [ ${exit_code} -le 159 ]; then + echo "FAIL: perf report crashed (signal $((exit_code - 128))) on ${cut_a= t}-byte truncated file" + err=3D1 + fi +done + +cleanup +exit ${err} --=20 2.54.0