From: Masami Hiramatsu (Google) <mhira...@kernel.org>

With CONFIG_DEBUG_INFO_BTF=y and PAHOLE_HAS_BTF_TAG=y, `__user` is
converted to `__attribute__((btf_type_tag("user")))`. In this case,
some syscall events have it for __user data, like below;

/sys/kernel/tracing # cat events/syscalls/sys_enter_openat/format
name: sys_enter_openat
ID: 720
format:
        field:unsigned short common_type;       offset:0;       size:2; 
signed:0;
        field:unsigned char common_flags;       offset:2;       size:1; 
signed:0;
        field:unsigned char common_preempt_count;       offset:3;       size:1; 
signed:0;
        field:int common_pid;   offset:4;       size:4; signed:1;

        field:int __syscall_nr; offset:8;       size:4; signed:1;
        field:int dfd;  offset:16;      size:8; signed:0;
        field:const char __attribute__((btf_type_tag("user"))) * filename;      
offset:24;      size:8; signed:0;
        field:int flags;        offset:32;      size:8; signed:0;
        field:umode_t mode;     offset:40;      size:8; signed:0;


Then the trace event filter fails to set the string acceptable flag
(FILTER_PTR_STRING) to the field and rejects setting string filter;

 # echo 'filename.ustring ~ "*ftracetest-dir.wbx24v*"' \
    >> events/syscalls/sys_enter_openat/filter
 sh: write error: Invalid argument
 # cat error_log
 [  723.743637] event filter parse error: error: Expecting numeric field
   Command: filename.ustring ~ "*ftracetest-dir.wbx24v*"

Since this __attribute__ makes format parsing complicated and not
needed, remove the __attribute__(.*) from the type string.

Signed-off-by: Masami Hiramatsu (Google) <mhira...@kernel.org>
---
Changes in v2:
 - Add memory allocation check flag.
 - Check the flag in update_event_fields() to avoid memory leak.
 - Fix 'static const int ... strlen()' issue.
 - Fix to find 2nd __attribute__ correctly. (adjust next after strcpy)
---
 kernel/trace/trace.h        |    1 +
 kernel/trace/trace_events.c |   74 ++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 67 insertions(+), 8 deletions(-)

diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index bd084953a98b..cd7be4ce6ee9 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -1468,6 +1468,7 @@ struct ftrace_event_field {
        int                     size;
        unsigned int            is_signed:1;
        unsigned int            needs_test:1;
+       unsigned int            alloc_type:1;
        int                     len;
 };
 
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 120531268abf..2e607afefff3 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -112,22 +112,74 @@ trace_find_event_field(struct trace_event_call *call, 
char *name)
        return __find_event_field(&ftrace_common_fields, name);
 }
 
-static int __trace_define_field(struct list_head *head, const char *type,
+#define ATTRIBUTE_STR "__attribute__"
+#define ATTRIBUTE_STR_LEN (sizeof(ATTRIBUTE_STR) - 1)
+
+/* Remove all __attribute__() from type */
+static void sanitize_field_type(char *type)
+{
+       char *attr, *tmp, *next;
+       int depth;
+
+       next = type;
+       while ((attr = strstr(next, ATTRIBUTE_STR))) {
+               next = attr + ATTRIBUTE_STR_LEN;
+
+               /* Retry if __attribute__ is a part of type name. */
+               if ((attr != type && !isspace(attr[-1])) ||
+                   *next != '(')
+                       continue;
+
+               depth = 0;
+               while ((tmp = strpbrk(next, "()"))) {
+                       if (*tmp == '(')
+                               depth++;
+                       else
+                               depth--;
+                       next = tmp + 1;
+                       if (depth == 0)
+                               break;
+               }
+               next = skip_spaces(next);
+               strcpy(attr, next);
+               next = attr;
+       }
+}
+
+static bool need_sanitize_field_type(const char *type)
+{
+       return !!strstr(type, ATTRIBUTE_STR);
+}
+
+static int __trace_define_field(struct list_head *head, const char *__type,
                                const char *name, int offset, int size,
                                int is_signed, int filter_type, int len,
                                int need_test)
 {
        struct ftrace_event_field *field;
+       char *type;
 
        field = kmem_cache_alloc(field_cachep, GFP_TRACE);
        if (!field)
                return -ENOMEM;
 
        field->name = name;
-       field->type = type;
+       if (need_sanitize_field_type(__type)) {
+               type = kstrdup(__type, GFP_KERNEL);
+               if (!type) {
+                       kfree(field);
+                       return -ENOMEM;
+               }
+               sanitize_field_type(type);
+               field->type = type;
+               field->alloc_type = 1;
+       } else {
+               field->type = __type;
+               field->alloc_type = 0;
+       }
 
        if (filter_type == FILTER_OTHER)
-               field->filter_type = filter_assign_type(type);
+               field->filter_type = filter_assign_type(field->type);
        else
                field->filter_type = filter_type;
 
@@ -225,6 +277,8 @@ static void trace_destroy_fields(struct trace_event_call 
*call)
        head = trace_get_fields(call);
        list_for_each_entry_safe(field, next, head, link) {
                list_del(&field->link);
+               if (field->alloc_type)
+                       kfree(field->type);
                kmem_cache_free(field_cachep, field);
        }
 }
@@ -3290,10 +3344,14 @@ static void update_event_fields(struct trace_event_call 
*call,
                if (strncmp(map->eval_string, ptr, len) != 0)
                        continue;
 
-               str = kstrdup(field->type, GFP_KERNEL);
-               if (WARN_ON_ONCE(!str))
-                       return;
-               ptr = str + (ptr - field->type);
+               if (!field->alloc_type) {
+                       str = kstrdup(field->type, GFP_KERNEL);
+                       if (WARN_ON_ONCE(!str))
+                               return;
+                       ptr = str + (ptr - field->type);
+               } else
+                       str = (char *)field->type;
+
                ptr = eval_replace(ptr, map, len);
                /* enum/sizeof string smaller than value */
                if (WARN_ON_ONCE(!ptr)) {
@@ -3306,7 +3364,7 @@ static void update_event_fields(struct trace_event_call 
*call,
                 * when the module is removed. Otherwise, it will stay allocated
                 * until a reboot.
                 */
-               if (call->module)
+               if (!field->alloc_type && call->module)
                        add_str_to_module(call->module, str);
 
                field->type = str;


Reply via email to