tracing/user_events: Fix non-spaced field matching
v2 cover tracing/user_events: Fix non-spaced field matching
When the ABI was updated to prevent same name w/different args, it missed an important corner case when fields don't end with a space. Typically, space is used for fields to help separate them, like "u8 field1; u8 field2". If no spaces are used, like
"u8 field1;u8 field2", then the parsing works for the first time. However, the match check fails on a subsequent register, leading to confusion.
This is because the match check uses argv_split() and assumes that all fields will be split upon the space. When spaces are used, we get back { "u8", "field1;" }, without spaces we get back { "u8", "field1;u8" }. This causes a mismatch, and the user program gets back -EADDRINUSE.
Add a method to detect this case before calling argv_split(). If found force a space after the field separator character ';'. This ensures all cases work properly for matching.
I could not find an existing function to accomplish this, so I had to hand code a copy with this logic. If there is a better way to achieve this, I'm all ears.
This series also adds a selftest to ensure this doesn't break again.
With this fix, the following are all treated as matching:
u8 field1;u8 field2
u8 field1; u8 field2
u8 field1;\tu8 field2
u8 field1;\nu8 field2
V2 changes:
Renamed fix_semis_no_space() to insert_space_after_semis(). Have user_event_argv_split() return fast in no-split case. Pulled in Masami's shorter loop in insert_space_after_semis().
Beau Belgrave (2):
tracing/user_events: Fix non-spaced field matching selftests/user_events: Add non-spacing separator check
kernel/trace/trace_events_user.c | 76 ++++++++++++++++++- .../selftests/user_events/ftrace_test.c | 8 ++ 2 files changed, 83 insertions(+), 1 deletion(-)
base-commit: 0bbac3facb5d6cc0171c45c9873a2dc96bea9680
Signature · 1 line
2.34.1
PATCH v2 1/2 tracing/user_events: Fix non-spaced field matching
When the ABI was updated to prevent same name w/different args, it missed an important corner case when fields don't end with a space. Typically, space is used for fields to help separate them, like "u8 field1; u8 field2". If no spaces are used, like
"u8 field1;u8 field2", then the parsing works for the first time. However, the match check fails on a subsequent register, leading to confusion.
This is because the match check uses argv_split() and assumes that all fields will be split upon the space. When spaces are used, we get back { "u8", "field1;" }, without spaces we get back { "u8", "field1;u8" }. This causes a mismatch, and the user program gets back -EADDRINUSE.
Add a method to detect this case before calling argv_split(). If found force a space after the field separator character ';'. This ensures all cases work properly for matching.
With this fix, the following are all treated as matching:
u8 field1;u8 field2
u8 field1; u8 field2
u8 field1;\tu8 field2
u8 field1;\nu8 field2
--- kernel/trace/trace_events_user.c | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-)
kernel/trace/trace_events_user.c +75 -1
@@ -1989,6 +1989,80 @@ static int user_event_set_tp_name(struct user_event *user)return 0 ; } +/* +* Counts how many ';' without a trailing space are in the args. +*/ +static int count_semis_no_space (char *args) +{ +int count =0 ; + +while ((args =strchr (args,';' ))) { + args++; + +if (!isspace (*args)) + count++; + } + +return count; +} + +/* +* Copies the arguments while ensuring all ';' have a trailing space. +*/ +static char *insert_space_after_semis (char *args,int count) +{ +char *fixed, *pos; +int len; + + len =strlen (args) + count; + fixed =kmalloc (len +1 ,GFP_KERNEL ); + +if (!fixed) +return NULL ; + + pos = fixed; + +/* Insert a space after ';' if there is no trailing space. */ +while (*args) { + *pos = *args++; + +if (*pos++ ==';' && !isspace (*args)) + *pos++ =' ' ; + } + + *pos ='\0' ; + +return fixed; +} + +static char **user_event_argv_split (char *args,int *argc) +{ +char **split; +char *fixed; +int count; + +/* Count how many ';' without a trailing space */ + count =count_semis_no_space (args); + +/* No fixup is required */ +if (!count) +return argv_split (GFP_KERNEL , args, argc); + +/* We must fixup 'field;field' to 'field; field' */ + fixed =insert_space_after_semis (args, count); + +if (!fixed) +return NULL ; + +/* We do a normal split afterwards */ + split =argv_split (GFP_KERNEL , fixed, argc); + +/* We can free since argv_split makes a copy */ +kfree (fixed); + +return split; +} +/* * Parses the event name, arguments and flags then registers if successful. * The name buffer lifetime is owned by this method for success cases only.
@@ -2012,7 +2086,7 @@ static int user_event_parse(struct user_event_group *group, char *name,return -EPERM ;if (args) { - argv =argv_split (GFP_KERNEL , args, &argc); + argv =user_event_argv_split (args, &argc);if (!argv)return -ENOMEM ;
Signature · 1 line
2.34.1
Steven Rostedt
maintainerQuoted 17 lines · On Tue, 23 Apr 2024 16:23:37 +0000 Beau Belgrave <[email protected]>…
When the ABI was updated to prevent same name w/different args, it missed an important corner case when fields don't end with a space. Typically, space is used for fields to help separate them, like "u8 field1; u8 field2". If no spaces are used, like
"u8 field1;u8 field2", then the parsing works for the first time. However, the match check fails on a subsequent register, leading to confusion.This is because the match check uses
argv_split()and assumes that all fields will be split upon the space. When spaces are used, we get back { "u8", "field1;" }, without spaces we get back { "u8", "field1;u8" }. This causes a mismatch, and the user program gets back-EADDRINUSE.Add a method to detect this case before calling
argv_split(). If found force a space after the field separator character ';'. This ensures all cases work properly for matching.
Quoted 5 lines
With this fix, the following are all treated as matching:
u8 field1;u8 field2
u8 field1; u8 field2
u8 field1;\tu8 field2
u8 field1;\nu8 field2
I'm curious, what happens if you have: "u8 field1; u8 field2;" ?
Do you care? As you will then create "u8 field1; u8 field2; "
but I'm guessing the extra whitespace at the end doesn't affect anything.
Quoted 17 lines
Fixes ba470eebc2f6 ("tracing/user_events: Prevent same name but different args event")Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-)kernel/trace/trace_events_user.c +2
-0@@ -1989,6 +1989,80 @@ static int user_event_set_tp_name(struct user_event *user)return 0 ; } +/* +* Counts how many ';' without a trailing space are in the args.
kernel/trace/trace_events_user.c static int user_event_set_tp_name(struct user_event *user) · line 2003
Quoted 10 lines
kernel/trace/trace_events_user.c +10
-0+*/ +static int count_semis_no_space (char *args) +{ +int count =0 ; + +while ((args =strchr (args,';' ))) { + args++; + +if (!isspace (*args)) + count++;
This will count that "..;"
This is most likely not an issue, but since I didn't see this case anywhere, I figured I bring it up just to confirm that it's not an issue.
-- Steve
Quoted 34 lines
kernel/trace/trace_events_user.c +34
-0+ } + +return count; +} + +/* +* Copies the arguments while ensuring all ';' have a trailing space. +*/ +static char *insert_space_after_semis (char *args,int count) +{ +char *fixed, *pos; +int len; + + len =strlen (args) + count; + fixed =kmalloc (len +1 ,GFP_KERNEL ); + +if (!fixed) +return NULL ; + + pos = fixed; + +/* Insert a space after ';' if there is no trailing space. */ +while (*args) { + *pos = *args++; + +if (*pos++ ==';' && !isspace (*args)) + *pos++ =' ' ; + } + + *pos ='\0' ; + +return fixed; +} +
Quoted 20 lines · Steven Rostedt
Quoted 17 lines · On Tue, 23 Apr 2024 16:23:37 +0000 Beau Belgrave <[email protected]>…
When the ABI was updated to prevent same name w/different args, it missed an important corner case when fields don't end with a space. Typically, space is used for fields to help separate them, like "u8 field1; u8 field2". If no spaces are used, like
"u8 field1;u8 field2", then the parsing works for the first time. However, the match check fails on a subsequent register, leading to confusion.This is because the match check uses
argv_split()and assumes that all fields will be split upon the space. When spaces are used, we get back { "u8", "field1;" }, without spaces we get back { "u8", "field1;u8" }. This causes a mismatch, and the user program gets back-EADDRINUSE.Add a method to detect this case before calling
argv_split(). If found force a space after the field separator character ';'. This ensures all cases work properly for matching.
Quoted 8 lines
Quoted 5 lines
With this fix, the following are all treated as matching:
u8 field1;u8 field2
u8 field1; u8 field2
u8 field1;\tu8 field2
u8 field1;\nu8 field2I'm curious, what happens if you have: "u8 field1; u8 field2;" ?
You'll get an extra whitespace during the copy, assuming it was really:
"u8 field1;u8 field2"
If it had spaces, this code wouldn't run.
Quoted 4 lines
Do you care? As you will then create "u8 field1; u8 field2; "
but I'm guessing the extra whitespace at the end doesn't affect anything.
Right, you get an extra byte allocated, but the argv_split() with ignore it. The compare will work correctly (I've verified this just now to double check).
IE these all match:
"Test u8 a; u8 b; "
"Test u8 a; u8 b;"
"Test u8 a; u8 b"
Quoted 31 lines
Quoted 18 lines
Fixes ba470eebc2f6 ("tracing/user_events: Prevent same name but different args event")Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-)kernel/trace/trace_events_user.c +2
-0@@ -1989,6 +1989,80 @@ static int user_event_set_tp_name(struct user_event *user)return 0 ; } +/* +* Counts how many ';' without a trailing space are in the args. kernel/trace/trace_events_user.c static int user_event_set_tp_name(struct user_event *user) · line 2003
Quoted 10 lines
kernel/trace/trace_events_user.c +10
-0+*/ +static int count_semis_no_space (char *args) +{ +int count =0 ; + +while ((args =strchr (args,';' ))) { + args++; + +if (!isspace (*args)) + count++;This will count that "..;"
Quoted 3 lines
This is most likely not an issue, but since I didn't see this case anywhere, I figured I bring it up just to confirm that it's not an issue.
It's not an issue on the matching/logic. However, you do get an extra byte alloc (which doesn't bother me in this edge case).
Thanks,
-Beau
Quoted 37 lines
-- Steve
Quoted 34 lines
kernel/trace/trace_events_user.c +34
-0+ } + +return count; +} + +/* +* Copies the arguments while ensuring all ';' have a trailing space. +*/ +static char *insert_space_after_semis (char *args,int count) +{ +char *fixed, *pos; +int len; + + len =strlen (args) + count; + fixed =kmalloc (len +1 ,GFP_KERNEL ); + +if (!fixed) +return NULL ; + + pos = fixed; + +/* Insert a space after ';' if there is no trailing space. */ +while (*args) { + *pos = *args++; + +if (*pos++ ==';' && !isspace (*args)) + *pos++ =' ' ; + } + + *pos ='\0' ; + +return fixed; +} +
Steven Rostedt
maintainerQuoted 2 lines · On Thu, 2 May 2024 15:58:53 -0700 Beau Belgrave <[email protected]>…
It's not an issue on the matching/logic. However, you do get an extra byte alloc (which doesn't bother me in this edge case).
Figured as much, but since there was no mention of it, I decided to bring it up.
I'll take this as-is then.
-- Steve
PATCH v2 2/2 selftests/user_events: Add non-spacing separator check
In reply to Beau BelgraveThe ABI documentation indicates that field separators do not need a space between them, only a ';'. When no spacing is used, the register must work. Any subsequent register, with or without spaces, must match and not return -EADDRINUSE.
Add a non-spacing separator case to our self-test register case to ensure it works going forward.
--- tools/testing/selftests/user_events/ftrace_test.c | 8 ++++++++ 1 file changed, 8 insertions(+)
tools/testing/selftests/user_events/ftrace_test.c +8 -0
@@ -261,6 +261,12 @@ TEST_F(user, register_events) {ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSREG , ®));ASSERT_EQ (0 , reg.write_index); +/* Register without separator spacing should still match */ + reg.enable_bit =29 ; + reg.name_args = (__u64 )"__test_event u32 field1;u32 field2" ; +ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSREG , ®)); +ASSERT_EQ (0 , reg.write_index); +/* Multiple registers to same name but different args should fail */ reg.enable_bit =29 ; reg.name_args = (__u64 )"__test_event u32 field1;" ;
@@ -288,6 +294,8 @@ TEST_F(user, register_events) {ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg)); unreg.disable_bit =30 ;ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg)); + unreg.disable_bit =29 ; +ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg));/* Delete should have been auto-done after close and unregister */ close (self->data_fd);
Signature · 1 line
2.34.1
Re: [PATCH v2 2/2] selftests/user_events: Add non-spacing separator check
Quoted 8 lines · On Tue, 23 Apr 2024 16:23:38 +0000 Beau Belgrave <[email protected]>…
The ABI documentation indicates that field separators do not need a space between them, only a ';'. When no spacing is used, the register must work. Any subsequent register, with or without spaces, must match and not return
-EADDRINUSE.Add a non-spacing separator case to our self-test register case to ensure it works going forward.
Looks good to me.
Thanks!
Quoted 34 lines
Signed-off-by Beau Belgrave <[email protected]>--- tools/testing/selftests/user_events/ftrace_test.c | 8 ++++++++ 1 file changed, 8 insertions(+)tools/testing/selftests/user_events/ftrace_test.c +8
-0@@ -261,6 +261,12 @@ TEST_F(user, register_events) {ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSREG , ®));ASSERT_EQ (0 , reg.write_index); +/* Register without separator spacing should still match */ + reg.enable_bit =29 ; + reg.name_args = (__u64 )"__test_event u32 field1;u32 field2" ; +ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSREG , ®)); +ASSERT_EQ (0 , reg.write_index); +/* Multiple registers to same name but different args should fail */ reg.enable_bit =29 ; reg.name_args = (__u64 )"__test_event u32 field1;" ;@@ -288,6 +294,8 @@ TEST_F(user, register_events) {ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg)); unreg.disable_bit =30 ;ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg)); + unreg.disable_bit =29 ; +ASSERT_EQ (0 ,ioctl (self->data_fd,DIAG_IOCSUNREG , &unreg));/* Delete should have been auto-done after close and unregister */ close (self->data_fd);Signature · 1 line
2.34.1
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>