mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 10:09:10 +02:00
perf session: Validate nr fields against event size on both swap and common paths
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 += 1 to size += 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. Changes in v2: - Document that PERF_RECORD_NAMESPACES max_nr includes trailing sample_id space when sample_id_all is present — harmless on the swap path because both per-element bswap_64 and swap_sample_id_all() perform the same u64 byte swap (Reported-by: sashiko-bot@kernel.org) Reported-by: sashiko-bot@kernel.org # Running on a local machine Reviewed-by: Ian Rogers <irogers@google.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Namhyung Kim <namhyung@kernel.org> Assisted-by: Claude:claude-opus-4.6-1m Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
6a38b515a5
commit
5682175baf
|
|
@ -496,13 +496,35 @@ 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;
|
||||
|
||||
event->namespaces.pid = bswap_32(event->namespaces.pid);
|
||||
event->namespaces.tid = bswap_32(event->namespaces.tid);
|
||||
event->namespaces.nr_namespaces = bswap_64(event->namespaces.nr_namespaces);
|
||||
|
||||
for (i = 0; i < event->namespaces.nr_namespaces; i++) {
|
||||
nr = event->namespaces.nr_namespaces;
|
||||
/*
|
||||
* Cannot underflow: perf_event__min_size[] guarantees header.size >= sizeof.
|
||||
* When sample_id_all is present max_nr slightly overestimates the
|
||||
* array space because header.size includes the trailing sample_id.
|
||||
* Harmless: both the per-element bswap_64 loop and swap_sample_id_all()
|
||||
* perform the same u64 byte swap, so the result is correct regardless
|
||||
* of where the boundary between array and sample_id falls.
|
||||
*/
|
||||
max_nr = (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 " exceeds payload (max %" PRIu64 "), clamping\n",
|
||||
nr, max_nr);
|
||||
nr = max_nr;
|
||||
event->namespaces.nr_namespaces = nr;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
struct perf_ns_link_info *ns = &event->namespaces.link_info[i];
|
||||
|
||||
ns->dev = bswap_64(ns->dev);
|
||||
|
|
@ -734,11 +756,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;
|
||||
|
||||
event->thread_map.nr = bswap_64(event->thread_map.nr);
|
||||
|
||||
for (i = 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 — downstream would get a wrong map.
|
||||
*/
|
||||
/* Cannot underflow: perf_event__min_size[] guarantees header.size >= sizeof */
|
||||
nr = event->thread_map.nr;
|
||||
if (nr > (event->header.size - sizeof(event->thread_map)) /
|
||||
sizeof(event->thread_map.entries[0]))
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < nr; i++)
|
||||
event->thread_map.entries[i].pid = bswap_64(event->thread_map.entries[i].pid);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -747,32 +781,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 = &event->cpu_map.data;
|
||||
u32 payload = event->header.size - sizeof(event->header);
|
||||
|
||||
data->type = bswap_16(data->type);
|
||||
|
||||
/*
|
||||
* 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 = bswap_16(data->cpus_data.nr);
|
||||
case PERF_CPU_MAP__CPUS: {
|
||||
u16 nr, max_nr;
|
||||
|
||||
for (unsigned i = 0; i < data->cpus_data.nr; i++)
|
||||
data->cpus_data.nr = bswap_16(data->cpus_data.nr);
|
||||
nr = data->cpus_data.nr;
|
||||
max_nr = (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 = max_nr;
|
||||
data->cpus_data.nr = nr;
|
||||
}
|
||||
for (unsigned int i = 0; i < nr; i++)
|
||||
data->cpus_data.cpu[i] = bswap_16(data->cpus_data.cpu[i]);
|
||||
break;
|
||||
}
|
||||
case PERF_CPU_MAP__MASK:
|
||||
data->mask32_data.long_size = bswap_16(data->mask32_data.long_size);
|
||||
|
||||
switch (data->mask32_data.long_size) {
|
||||
case 4:
|
||||
case 4: {
|
||||
u16 nr, max_nr;
|
||||
|
||||
data->mask32_data.nr = bswap_16(data->mask32_data.nr);
|
||||
for (unsigned i = 0; i < data->mask32_data.nr; i++)
|
||||
nr = data->mask32_data.nr;
|
||||
max_nr = (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 = max_nr;
|
||||
data->mask32_data.nr = nr;
|
||||
}
|
||||
for (unsigned int i = 0; i < nr; i++)
|
||||
data->mask32_data.mask[i] = bswap_32(data->mask32_data.mask[i]);
|
||||
break;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
u16 nr, max_nr;
|
||||
|
||||
data->mask64_data.nr = bswap_16(data->mask64_data.nr);
|
||||
for (unsigned i = 0; i < data->mask64_data.nr; i++)
|
||||
nr = data->mask64_data.nr;
|
||||
if (payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mask)) {
|
||||
data->mask64_data.nr = 0;
|
||||
break;
|
||||
}
|
||||
max_nr = (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 = max_nr;
|
||||
data->mask64_data.nr = nr;
|
||||
}
|
||||
for (unsigned int i = 0; i < nr; i++)
|
||||
data->mask64_data.mask[i] = 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:
|
||||
|
|
@ -788,11 +870,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;
|
||||
|
||||
size = bswap_64(event->stat_config.nr) * sizeof(event->stat_config.data[0]);
|
||||
size += 1; /* nr item itself */
|
||||
nr = bswap_64(event->stat_config.nr);
|
||||
/* Cannot underflow: perf_event__min_size[] guarantees header.size >= sizeof */
|
||||
max_nr = (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 payload (max %" PRIu64 "), clamping\n",
|
||||
nr, max_nr);
|
||||
nr = max_nr;
|
||||
}
|
||||
size = nr * sizeof(event->stat_config.data[0]);
|
||||
/* The swap starts at &nr, so add its size to cover the full range */
|
||||
size += 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 = nr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1730,8 +1828,27 @@ 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 >= sizeof.
|
||||
* Includes trailing sample_id space when present, but prevents OOB.
|
||||
*/
|
||||
u64 max_nr = (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,
|
||||
|
|
@ -1912,15 +2029,112 @@ static s64 perf_session__process_user_event(struct perf_session *session,
|
|||
perf_session__auxtrace_error_inc(session, event);
|
||||
err = 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 = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
max_nr = (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 = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
err = tool->thread_map(tool, session, event);
|
||||
break;
|
||||
case PERF_RECORD_CPU_MAP:
|
||||
}
|
||||
case PERF_RECORD_CPU_MAP: {
|
||||
struct perf_record_cpu_map_data *data = &event->cpu_map.data;
|
||||
u32 payload = 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 = (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 = 0;
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PERF_CPU_MAP__MASK:
|
||||
if (data->mask32_data.long_size == 4) {
|
||||
u16 max_nr = (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 payload (max %u), skipping\n",
|
||||
data->mask32_data.nr, max_nr);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
} else if (data->mask64_data.long_size == 8) {
|
||||
u16 max_nr;
|
||||
|
||||
if (payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mask)) {
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
max_nr = (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 payload (max %u), skipping\n",
|
||||
data->mask64_data.nr, max_nr);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
pr_warning("WARNING: PERF_RECORD_CPU_MAP: unsupported long_size %u, skipping\n",
|
||||
data->mask32_data.long_size);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
err = 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 >= sizeof */
|
||||
u64 max_nr = (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 payload (max %" PRIu64 "), skipping\n",
|
||||
(u64)event->stat_config.nr, max_nr);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = tool->stat_config(tool, session, event);
|
||||
break;
|
||||
}
|
||||
case PERF_RECORD_STAT:
|
||||
err = tool->stat(tool, session, event);
|
||||
break;
|
||||
|
|
@ -1963,6 +2177,7 @@ static s64 perf_session__process_user_event(struct perf_session *session,
|
|||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
perf_sample__exit(&sample);
|
||||
return err;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user