perf: Fix perf_event_validate_size()
authorPeter Zijlstra <peterz@infradead.org>
Wed, 29 Nov 2023 14:24:52 +0000 (15:24 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 13 Dec 2023 17:39:24 +0000 (18:39 +0100)
[ Upstream commit 382c27f4ed28f803b1f1473ac2d8db0afc795a1b ]

Budimir noted that perf_event_validate_size() only checks the size of
the newly added event, even though the sizes of all existing events
can also change due to not all events having the same read_format.

When we attach the new event, perf_group_attach(), we do re-compute
the size for all events.

Fixes: a723968c0ed3 ("perf: Fix u16 overflows")
Reported-by: Budimir Markovic <markovicbudimir@gmail.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
kernel/events/core.c

index 8f2b9d8..0193243 100644 (file)
@@ -1812,31 +1812,34 @@ static inline void perf_event__state_init(struct perf_event *event)
                                              PERF_EVENT_STATE_INACTIVE;
 }
 
-static void __perf_event_read_size(struct perf_event *event, int nr_siblings)
+static int __perf_event_read_size(u64 read_format, int nr_siblings)
 {
        int entry = sizeof(u64); /* value */
        int size = 0;
        int nr = 1;
 
-       if (event->attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+       if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
                size += sizeof(u64);
 
-       if (event->attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+       if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
                size += sizeof(u64);
 
-       if (event->attr.read_format & PERF_FORMAT_ID)
+       if (read_format & PERF_FORMAT_ID)
                entry += sizeof(u64);
 
-       if (event->attr.read_format & PERF_FORMAT_LOST)
+       if (read_format & PERF_FORMAT_LOST)
                entry += sizeof(u64);
 
-       if (event->attr.read_format & PERF_FORMAT_GROUP) {
+       if (read_format & PERF_FORMAT_GROUP) {
                nr += nr_siblings;
                size += sizeof(u64);
        }
 
-       size += entry * nr;
-       event->read_size = size;
+       /*
+        * Since perf_event_validate_size() limits this to 16k and inhibits
+        * adding more siblings, this will never overflow.
+        */
+       return size + nr * entry;
 }
 
 static void __perf_event_header_size(struct perf_event *event, u64 sample_type)
@@ -1886,8 +1889,9 @@ static void __perf_event_header_size(struct perf_event *event, u64 sample_type)
  */
 static void perf_event__header_size(struct perf_event *event)
 {
-       __perf_event_read_size(event,
-                              event->group_leader->nr_siblings);
+       event->read_size =
+               __perf_event_read_size(event->attr.read_format,
+                                      event->group_leader->nr_siblings);
        __perf_event_header_size(event, event->attr.sample_type);
 }
 
@@ -1918,24 +1922,35 @@ static void perf_event__id_header_size(struct perf_event *event)
        event->id_header_size = size;
 }
 
+/*
+ * Check that adding an event to the group does not result in anybody
+ * overflowing the 64k event limit imposed by the output buffer.
+ *
+ * Specifically, check that the read_size for the event does not exceed 16k,
+ * read_size being the one term that grows with groups size. Since read_size
+ * depends on per-event read_format, also (re)check the existing events.
+ *
+ * This leaves 48k for the constant size fields and things like callchains,
+ * branch stacks and register sets.
+ */
 static bool perf_event_validate_size(struct perf_event *event)
 {
-       /*
-        * The values computed here will be over-written when we actually
-        * attach the event.
-        */
-       __perf_event_read_size(event, event->group_leader->nr_siblings + 1);
-       __perf_event_header_size(event, event->attr.sample_type & ~PERF_SAMPLE_READ);
-       perf_event__id_header_size(event);
+       struct perf_event *sibling, *group_leader = event->group_leader;
 
-       /*
-        * Sum the lot; should not exceed the 64k limit we have on records.
-        * Conservative limit to allow for callchains and other variable fields.
-        */
-       if (event->read_size + event->header_size +
-           event->id_header_size + sizeof(struct perf_event_header) >= 16*1024)
+       if (__perf_event_read_size(event->attr.read_format,
+                                  group_leader->nr_siblings + 1) > 16*1024)
                return false;
 
+       if (__perf_event_read_size(group_leader->attr.read_format,
+                                  group_leader->nr_siblings + 1) > 16*1024)
+               return false;
+
+       for_each_sibling_event(sibling, group_leader) {
+               if (__perf_event_read_size(sibling->attr.read_format,
+                                          group_leader->nr_siblings + 1) > 16*1024)
+                       return false;
+       }
+
        return true;
 }