user_event_parse_field() accepts large structure sizes but accumulates
field offsets in a u32 before storing them in signed int fields.  A
declaration can wrap the recorded minimum event size while a fixed-width
field retains an offset outside the accepted record.  A short write is
then accepted and a field filter reads beyond the event buffer.

KASAN reports:

  BUG: KASAN: slab-use-after-free in filter_match_preds
  Read of size 8
  filter_match_preds
  trace_event_buffer_commit
  user_event_ftrace
  user_events_write_core

Parse an explicit structure size into a u32, reject values greater than
INT_MAX before converting them to int, and use checked addition for the
resulting field offset.  This prevents both signed conversion and offset
overflow from bypassing the downstream bounds checks.

Fixes: 7f5a08c79df3 ("user_events: Add minimal support for trace_event into 
ftrace")
Signed-off-by: Yifan Zhang <[email protected]>
---
 kernel/trace/trace_events_user.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index 85938b45b7618..04eef4865490b 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -1207,6 +1207,7 @@ static int user_event_parse_field(char *field, struct 
user_event *user,
 {
        char *part, *type, *name;
        u32 depth = 0, saved_offset = *offset;
+       u32 new_offset, struct_size;
        int len, size = -EINVAL;
        bool is_struct = false;
 
@@ -1267,8 +1268,11 @@ static int user_event_parse_field(char *field, struct 
user_event *user,
                        if (!is_struct)
                                return -EINVAL;
 
-                       if (kstrtou32(part, 10, &size))
+                       if (kstrtou32(part, 10, &struct_size))
                                return -EINVAL;
+                       if (struct_size > INT_MAX)
+                               return -E2BIG;
+                       size = struct_size;
                        break;
                default:
                        return -EINVAL;
@@ -1287,7 +1291,11 @@ static int user_event_parse_field(char *field, struct 
user_event *user,
        if (size < 0)
                return size;
 
-       *offset = saved_offset + size;
+       if (check_add_overflow(saved_offset, (u32)size, &new_offset) ||
+           new_offset > INT_MAX)
+               return -E2BIG;
+
+       *offset = new_offset;
 
        return user_event_add_field(user, type, name, saved_offset, size,
                                    type[0] != 'u', FILTER_OTHER);
-- 
2.39.5 (Apple Git-154)

Reply via email to