switch (ev->type) {
case PERF_EVENT_UPDATE__SCALE:
+ if (event->header.size < offsetof(struct perf_record_event_update, scale) +
+ sizeof(ev->scale)) {
+ ret += fprintf(fp, "... scale: (truncated)\n");
+ break;
+ }
ret += fprintf(fp, "... scale: %f\n", ev->scale.scale);
break;
case PERF_EVENT_UPDATE__UNIT:
- ret += fprintf(fp, "... unit: %s\n", ev->unit);
- break;
- case PERF_EVENT_UPDATE__NAME:
- ret += fprintf(fp, "... name: %s\n", ev->name);
+ case PERF_EVENT_UPDATE__NAME: {
+ size_t str_off = offsetof(struct perf_record_event_update, unit);
+ size_t max_len = event->header.size > str_off ?
+ event->header.size - str_off : 0;
+
+ if (max_len == 0 || strnlen(ev->unit, max_len) == max_len) {
+ ret += fprintf(fp, "... %s: (unterminated)\n",
+ ev->type == PERF_EVENT_UPDATE__UNIT ? "unit" : "name");
+ break;
+ }
+ ret += fprintf(fp, "... %s: %s\n",
+ ev->type == PERF_EVENT_UPDATE__UNIT ? "unit" : "name",
+ ev->unit);
break;
- case PERF_EVENT_UPDATE__CPUS:
+ }
+ case PERF_EVENT_UPDATE__CPUS: {
+ size_t cpus_off = 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 += fprintf(fp, "... cpus: (truncated)\n");
+ break;
+ }
+
+ /*
+ * Validate nr against payload — this function may be
+ * called from the stub handler (dump_trace path) which
+ * bypasses perf_event__process_event_update() validation.
+ */
+ cpus_payload = event->header.size - cpus_off;
+ if (ev->cpus.cpus.type == PERF_CPU_MAP__CPUS) {
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, cpus_data.cpu) ||
+ 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 += fprintf(fp, "... cpus: nr %u exceeds payload\n",
+ ev->cpus.cpus.cpus_data.nr);
+ break;
+ }
+ } else if (ev->cpus.cpus.type == PERF_CPU_MAP__MASK) {
+ if (ev->cpus.cpus.mask32_data.long_size == 4) {
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask32_data.mask) ||
+ ev->cpus.cpus.mask32_data.nr >
+ (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask32_data.mask)) /
+ sizeof(ev->cpus.cpus.mask32_data.mask[0])) {
+ ret += fprintf(fp, "... cpus: mask nr %u exceeds payload\n",
+ ev->cpus.cpus.mask32_data.nr);
+ break;
+ }
+ } else if (ev->cpus.cpus.mask64_data.long_size == 8) {
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mask) ||
+ ev->cpus.cpus.mask64_data.nr >
+ (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask64_data.mask)) /
+ sizeof(ev->cpus.cpus.mask64_data.mask[0])) {
+ ret += fprintf(fp, "... cpus: mask nr %u exceeds payload\n",
+ ev->cpus.cpus.mask64_data.nr);
+ break;
+ }
+ }
+ }
+
ret += fprintf(fp, "... ");
map = cpu_map__new_data(&ev->cpus.cpus);
} else
ret += fprintf(fp, "failed to get cpus\n");
break;
+ }
default:
ret += fprintf(fp, "... unknown type\n");
break;
struct evsel *evsel;
struct perf_cpu_map *map;
+ /*
+ * Validate payload before dump_trace or processing — both
+ * paths access variant-specific fields without further checks.
+ */
+ if (ev->type == PERF_EVENT_UPDATE__UNIT ||
+ ev->type == PERF_EVENT_UPDATE__NAME) {
+ size_t str_off = offsetof(struct perf_record_event_update, unit);
+ size_t max_len = event->header.size > str_off ?
+ event->header.size - str_off : 0;
+
+ if (max_len == 0 || strnlen(ev->unit, max_len) == max_len) {
+ pr_warning("WARNING: PERF_RECORD_EVENT_UPDATE: %s not null-terminated, skipping\n",
+ ev->type == PERF_EVENT_UPDATE__UNIT ? "unit" : "name");
+ return 0;
+ }
+ } else if (ev->type == 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 == PERF_EVENT_UPDATE__CPUS) {
+ size_t cpus_off = offsetof(struct perf_record_event_update, cpus);
+ size_t min_cpus = 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 — 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 = 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, skipping\n",
+ ev->cpus.cpus.cpus_data.nr);
+ return 0;
+ }
+ break;
+ case PERF_CPU_MAP__MASK:
+ if (ev->cpus.cpus.mask32_data.long_size == 4) {
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask32_data.mask) ||
+ ev->cpus.cpus.mask32_data.nr >
+ (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask32_data.mask)) /
+ sizeof(ev->cpus.cpus.mask32_data.mask[0])) {
+ pr_warning("WARNING: EVENT_UPDATE MASK: nr %u exceeds payload, skipping\n",
+ ev->cpus.cpus.mask32_data.nr);
+ return 0;
+ }
+ } else if (ev->cpus.cpus.mask64_data.long_size == 8) {
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mask) ||
+ ev->cpus.cpus.mask64_data.nr >
+ (cpus_payload - offsetof(struct perf_record_cpu_map_data, mask64_data.mask)) /
+ sizeof(ev->cpus.cpus.mask64_data.mask[0])) {
+ pr_warning("WARNING: EVENT_UPDATE MASK: nr %u exceeds payload, skipping\n",
+ ev->cpus.cpus.mask64_data.nr);
+ return 0;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
if (dump_trace)
perf_event__fprintf_event_update(event, stdout);
evsel->core.pmu_cpus = map;
} else
pr_err("failed to get event_update cpus\n");
+ break;
default:
break;
}
static int perf_event__event_update_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
- event->event_update.type = bswap_64(event->event_update.type);
- event->event_update.id = bswap_64(event->event_update.id);
+ struct perf_record_event_update *ev = &event->event_update;
+
+ ev->type = bswap_64(ev->type);
+ ev->id = bswap_64(ev->id);
+
+ /*
+ * Swap variant-specific fields so the processing path
+ * sees native byte order.
+ */
+ if (ev->type == 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 == PERF_EVENT_UPDATE__CPUS) {
+ u32 cpus_payload;
+ struct perf_record_cpu_map_data *data = &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 = event->header.size - offsetof(struct perf_record_event_update, cpus);
+ data->type = bswap_16(data->type);
+ /*
+ * Full swap including array elements — 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 = bswap_16(data->cpus_data.nr);
+ nr = data->cpus_data.nr;
+ max_nr = (cpus_payload - offsetof(struct perf_record_cpu_map_data,
+ cpus_data.cpu)) /
+ sizeof(data->cpus_data.cpu[0]);
+ if (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: {
+ u16 nr, max_nr;
+
+ data->mask32_data.nr = bswap_16(data->mask32_data.nr);
+ nr = data->mask32_data.nr;
+ max_nr = (cpus_payload - offsetof(struct perf_record_cpu_map_data,
+ mask32_data.mask)) /
+ sizeof(data->mask32_data.mask[0]);
+ if (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: {
+ u16 nr, max_nr;
+
+ data->mask64_data.nr = bswap_16(data->mask64_data.nr);
+ nr = data->mask64_data.nr;
+ if (cpus_payload < offsetof(struct perf_record_cpu_map_data, mask64_data.mask)) {
+ data->mask64_data.nr = 0;
+ break;
+ }
+ max_nr = (cpus_payload - offsetof(struct perf_record_cpu_map_data,
+ mask64_data.mask)) /
+ sizeof(data->mask64_data.mask[0]);
+ if (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:
+ break;
+ }
+ break;
+ case PERF_CPU_MAP__RANGE_CPUS:
+ data->range_cpu_data.start_cpu = bswap_16(data->range_cpu_data.start_cpu);
+ data->range_cpu_data.end_cpu = bswap_16(data->range_cpu_data.end_cpu);
+ break;
+ default:
+ break;
+ }
+ }
return 0;
}