* [PATCH v2 1/9] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
@ 2026-10-25 8:37 ` Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 2/9] tracing/filters: Fix false success return in process_system_preds() Masami Hiramatsu (Google)
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:37 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
regex_match_end() calculated string length as "len - 1", assuming @len is
the string length including trailing '\0'. However, for fixed-size string
fields, filter_pred_string() passes field->size as @len, and for "comm",
filter_pred_comm() passes TASK_COMM_LEN as @len.
Because @len in these cases represents the buffer capacity rather than the
actual length of the string, regex_match_end() tests against the tail of
the buffer (typically trailing '\0' padding) rather than the end of the
string. Furthermore, if @len is 0 (denoting an empty dynamic array without
trailing '\0'), "len - 1" underflows to -1.
First check "len < r->len" and return 0, which safely rejects negative
lengths and cases where the buffer capacity is smaller than the pattern.
Then compute the actual string length using strnlen(str, len), which
bounds the scan by @len (stopping at the first '\0') without dereferencing
past the buffer.
Fixes: a3291c14ecf0 ("tracing/filters: Fix MATCH_END_ONLY filter matching")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v2:
- Check "len < r->len" to guard against negative lengths and avoid
calling strnlen() when the buffer capacity is smaller than the pattern.
- Use strnlen(str, len) directly instead of calling strlen(str) when
len is 0, avoiding an out-of-bounds read on 0-length dynamic arrays.
---
kernel/trace/trace_events_filter.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 5e74d2dae40a..fac51a2953dd 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1075,10 +1075,14 @@ static int regex_match_middle(char *str, struct regex *r, int len)
static int regex_match_end(char *str, struct regex *r, int len)
{
- int strlen = len - 1;
+ int slen;
- if (strlen >= r->len &&
- memcmp(str + strlen - r->len, r->pattern, r->len) == 0)
+ if (len < r->len)
+ return 0;
+
+ slen = strnlen(str, len);
+ if (slen >= r->len &&
+ memcmp(str + slen - r->len, r->pattern, r->len) == 0)
return 1;
return 0;
}
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 2/9] tracing/filters: Fix false success return in process_system_preds()
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 1/9] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
@ 2026-10-25 8:37 ` Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 3/9] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison Masami Hiramatsu (Google)
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:37 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
In process_system_preds(), "fail" is initialized to true to indicate
whether any event in the subsystem successfully applied the filter.
However, "fail = false;" was placed at the end of the event loop outside
the error check, unconditionally clearing "fail" on every iteration even
when process_preds() returned an error.
As a result, if every event in the subsystem fails to match the filter
(e.g., an invalid field), process_system_preds() returns 0 (success)
instead of -EINVAL. Consequently, create_system_filter() considers the
operation successful and silently resets the subsystem filter display to
the default global filter message instead of reporting the parse error.
Move "fail = false;" into the else branch of the error check so that
"fail" is only cleared when at least one event filter succeeds. Because
file->filter is still replaced even when process_preds() fails, check
!list_empty(&filter_list->list) on the fail and fail_mem paths so any
replaced old filters are freed after an RCU grace period via
delay_free_filter().
Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index fac51a2953dd..54144ef9d0db 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2336,8 +2336,10 @@ static int process_system_preds(struct trace_subsystem_dir *dir,
filter_disable(file);
parse_error(pe, FILT_ERR_BAD_SUBSYS_FILTER, 0);
append_filter_err(tr, pe, filter);
- } else
+ } else {
event_set_filtered_flag(file);
+ fail = false;
+ }
filter_item = kzalloc_obj(*filter_item);
@@ -2352,8 +2354,6 @@ static int process_system_preds(struct trace_subsystem_dir *dir,
filter_item->filter = event_filter(file);
event_set_filter(file, filter);
filter = NULL;
-
- fail = false;
}
if (fail)
@@ -2368,14 +2368,17 @@ static int process_system_preds(struct trace_subsystem_dir *dir,
return 0;
fail:
/* No call succeeded */
- free_filter_list(filter_list);
+ if (!list_empty(&filter_list->list))
+ delay_free_filter(filter_list);
+ else
+ free_filter_list(filter_list);
parse_error(pe, FILT_ERR_BAD_SUBSYS_FILTER, 0);
return -EINVAL;
fail_mem:
__free_filter(filter);
- /* If any call succeeded, we still need to sync */
- if (!fail)
+ /* If any filter was replaced, we still need to sync */
+ if (!list_empty(&filter_list->list))
delay_free_filter(filter_list);
else
free_filter_list(filter_list);
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 3/9] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 1/9] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 2/9] tracing/filters: Fix false success return in process_system_preds() Masami Hiramatsu (Google)
@ 2026-10-25 8:37 ` Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 4/9] tracing/filters: Report parse error on all -EINVAL paths in filter parsing Masami Hiramatsu (Google)
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:37 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
In filter_pred_##size##_cpumask(), if the event's scalar CPU value is
out of range (cpu >= nr_cpu_ids), it unconditionally returns 0.
However, if the predicate operation is OP_NE (e.g., field != CPUS{0,1}),
an out-of-range CPU cannot match any valid CPU in the user-provided
cpumask and the condition is satisfied. Returning 0 produces a false
negative.
Return "pred->op == OP_NE" instead of 0 when cpu >= nr_cpu_ids.
Fixes: 3cbec9d7b93d ("tracing/filters: Enable filtering a scalar field by a cpumask")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 54144ef9d0db..44ca7772ed2c 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -759,7 +759,7 @@ static int filter_pred_##size##_cpumask(struct filter_pred *pred, void *event) \
unsigned int cpu = *addr; \
\
if (cpu >= nr_cpu_ids) \
- return 0; \
+ return pred->op == OP_NE; \
\
return do_filter_scalar_cpumask(pred->op, cpu, pred->mask); \
}
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 4/9] tracing/filters: Report parse error on all -EINVAL paths in filter parsing
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (2 preceding siblings ...)
2026-10-25 8:37 ` [PATCH v2 3/9] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison Masami Hiramatsu (Google)
@ 2026-10-25 8:37 ` Masami Hiramatsu (Google)
2026-10-25 8:37 ` [PATCH v2 5/9] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:37 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Several error paths in filter parsing return -EINVAL (or -1) without
calling parse_error() to set pe->lasterr:
1. In parse_pred(), when an invalid token is encountered where a field name
is expected (e.g., "common_pid == 1 || (common_pid == 2 && )"), len is 0
and it returns -1 without calling parse_error().
2. In process_preds(), when !nr_preds (e.g., empty or whitespace-only
filter), it returns -EINVAL before predicate_parse() can set
FILT_ERR_NO_FILTER.
3. In predicate_parse(), when op_stack overflows or the jump target check
fails, it returns -EINVAL without calling parse_error().
Because pe->lasterr remains 0 on these paths, append_filter_err() falls
back to printing an uninformative "Error: (0)" instead of reporting the
diagnostic and error position.
Call parse_error() with the appropriate error code on all of these paths.
Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 44ca7772ed2c..cc3a53e7cdbd 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -515,6 +515,8 @@ predicate_parse(const char *str, int nr_parens, int nr_preds,
switch (*next) {
case '(': /* #2 */
if (top - op_stack > nr_parens) {
+ parse_error(pe, FILT_ERR_TOO_MANY_OPEN,
+ next - str);
ret = -EINVAL;
goto out_free;
}
@@ -638,6 +640,7 @@ predicate_parse(const char *str, int nr_parens, int nr_preds,
prog[i].when_to_branch = invert;
/* Make sure the program always moves forward */
if (WARN_ON(prog[i].target <= i)) {
+ parse_error(pe, FILT_ERR_INVALID_FILTER, 0);
ret = -EINVAL;
goto out_free;
}
@@ -1689,8 +1692,10 @@ static int parse_pred(const char *str, void *data,
len = i - s;
- if (!len)
- return -1;
+ if (!len) {
+ parse_error(pe, FILT_ERR_INVALID_FILTER, pos + i);
+ return -EINVAL;
+ }
field_name = kmemdup_nul(str + s, len, GFP_KERNEL);
if (!field_name)
@@ -2278,8 +2283,10 @@ static int process_preds(struct trace_event_call *call,
return ret;
}
- if (!nr_preds)
+ if (!nr_preds) {
+ parse_error(pe, FILT_ERR_NO_FILTER, 0);
return -EINVAL;
+ }
prog = predicate_parse(filter_string, nr_parens, nr_preds,
parse_pred, call, pe);
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 5/9] tracing/filters: Fix function event operand parsing in parse_pred()
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (3 preceding siblings ...)
2026-10-25 8:37 ` [PATCH v2 4/9] tracing/filters: Report parse error on all -EINVAL paths in filter parsing Masami Hiramatsu (Google)
@ 2026-10-25 8:37 ` Masami Hiramatsu (Google)
2026-10-26 20:32 ` Steven Rostedt
2026-10-25 8:38 ` [PATCH v2 6/9] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
` (3 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:37 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
In parse_pred(), the ftrace_event_is_function(call) branch has several
bugs when parsing the string operand:
1. The loop 'for (i++; str[i]; i++)' unconditionally increments 'i' before
checking whether str[i] is the NUL terminator. When the operand is empty
(e.g. 'ip == '), this advances 'i' past the NUL terminator and reads
out-of-bounds memory until another zero byte is encountered.
2. When the operand is quoted (q != 0), the loop breaks with str[i] at the
closing quote, but 'i' is never incremented past it (unlike the regular
string literal parser which does 'i++'). Furthermore, it does not check
for a missing closing quote (!str[i]). Returning 'i' at the closing
quote causes predicate_parse() to immediately re-invoke parse_pred() on
the closing quote and fail with FILT_ERR_TOO_MANY_PREDS.
3. If the operand length is 0 (e.g. 'ip == )' or 'ip == ""'), it accepts an
empty pattern instead of rejecting it with FILT_ERR_INVALID_VALUE.
4. When the operand is unquoted (q == 0), stray quotes or '(' inside the
operand are consumed as part of the pattern, desynchronizing
parse_pred() from calc_stack().
Fix this by advancing 'i' and 's' past the opening quote before the loop,
starting the loop at the current 'i' without a blind increment, checking
for missing closing quotes, advancing 'i' past the closing quote, and
rejecting empty operands or stray quotes/'(' in unquoted operands.
Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 37 ++++++++++++++++++++++++++----------
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index cc3a53e7cdbd..3bd0b47a627f 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1834,22 +1834,39 @@ static int parse_pred(const char *str, void *data,
* Quotes are not required, but if they exist then we need
* to read them till we hit a matching one.
*/
- if (str[i] == '\'' || str[i] == '"')
- q = str[i];
- else
+ if (str[i] == '\'' || str[i] == '"') {
+ q = str[i++];
+ s = i;
+ } else {
q = 0;
+ }
- for (i++; str[i]; i++) {
+ for (; str[i]; i++) {
if (q && str[i] == q)
break;
- if (!q && (str[i] == ')' || str[i] == '&' ||
- str[i] == '|'))
- break;
+ if (!q) {
+ if (str[i] == ')' || str[i] == '&' ||
+ str[i] == '|')
+ break;
+ if (str[i] == '(' || str[i] == '\'' ||
+ str[i] == '"') {
+ parse_error(pe, FILT_ERR_INVALID_VALUE,
+ pos + i);
+ goto err_free;
+ }
+ }
+ }
+ if (q && !str[i]) {
+ parse_error(pe, FILT_ERR_MISSING_QUOTE, pos + i);
+ goto err_free;
}
- /* Skip quotes */
- if (q)
- s++;
len = i - s;
+ if (q)
+ i++;
+ if (!len) {
+ parse_error(pe, FILT_ERR_INVALID_VALUE, pos + i);
+ goto err_free;
+ }
if (len >= MAX_FILTER_STR_VAL) {
parse_error(pe, FILT_ERR_OPERAND_TOO_LONG, pos + i);
goto err_free;
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v2 5/9] tracing/filters: Fix function event operand parsing in parse_pred()
2026-10-25 8:37 ` [PATCH v2 5/9] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
@ 2026-10-26 20:32 ` Steven Rostedt
0 siblings, 0 replies; 13+ messages in thread
From: Steven Rostedt @ 2026-10-26 20:32 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: Shuah Khan, Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest
On Sun, 25 Oct 2026 17:37:56 +0900
"Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
> From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
>
> In parse_pred(), the ftrace_event_is_function(call) branch has several
> bugs when parsing the string operand:
>
> 1. The loop 'for (i++; str[i]; i++)' unconditionally increments 'i' before
> checking whether str[i] is the NUL terminator. When the operand is empty
> (e.g. 'ip == '), this advances 'i' past the NUL terminator and reads
> out-of-bounds memory until another zero byte is encountered.
Can't this part simply be fixed with:
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 5e74d2dae40a..6a14c964c115 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1733,6 +1733,11 @@ static int parse_pred(const char *str, void *data,
while (isspace(str[i]))
i++;
+ if (!str[i]) {
+ parse_error(pe, FILT_ERR_INVALID_FILTER, pos + i);
+ goto err_free;
+ }
+
s = i;
pred = kzalloc_obj(*pred);
>
> 2. When the operand is quoted (q != 0), the loop breaks with str[i] at the
> closing quote, but 'i' is never incremented past it (unlike the regular
> string literal parser which does 'i++'). Furthermore, it does not check
> for a missing closing quote (!str[i]). Returning 'i' at the closing
> quote causes predicate_parse() to immediately re-invoke parse_pred() on
> the closing quote and fail with FILT_ERR_TOO_MANY_PREDS.
This is a separate bug and needs to be a separate patch.
>
> 3. If the operand length is 0 (e.g. 'ip == )' or 'ip == ""'), it accepts an
> empty pattern instead of rejecting it with FILT_ERR_INVALID_VALUE.
Well, empty value ("") is actually valid. May be meaningless, but it's not
wrong.
>
> 4. When the operand is unquoted (q == 0), stray quotes or '(' inside the
> operand are consumed as part of the pattern, desynchronizing
> parse_pred() from calc_stack().
This is a separate bug and needs to be a separate patch, although it may be
the same as #2 above.
-- Steve
>
> Fix this by advancing 'i' and 's' past the opening quote before the loop,
> starting the loop at the current 'i' without a blind increment, checking
> for missing closing quotes, advancing 'i' past the closing quote, and
> rejecting empty operands or stray quotes/'(' in unquoted operands.
>
> Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
> Cc: stable@vger.kernel.org
> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> ---
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 6/9] tracing/filters: Skip second character of '&&' and '||' in calc_stack()
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (4 preceding siblings ...)
2026-10-25 8:37 ` [PATCH v2 5/9] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
@ 2026-10-25 8:38 ` Masami Hiramatsu (Google)
2026-10-26 20:47 ` Steven Rostedt
2026-10-25 8:38 ` [PATCH v2 7/9] tracing/filters: Reject unsupported field sizes in parse_pred() Masami Hiramatsu (Google)
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:38 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
In calc_stack(), when encountering '||' or '&&' (where str[i+1] == str[i]),
the loop sets 'is_pred = false' and continues without advancing 'i' past
the second character of the operator. On the next iteration, the second '|'
or '&' sees str[i+1] != str[i], breaks out of the switch statement, and is
counted as the start of a new predicate (incrementing nr_preds and setting
is_pred = true).
Consequently, a trailing logical operator (e.g. 'common_pid == 1 ||') is
counted as having 2 predicates instead of 1, and any subsequent non-space
character before a delimiter fails to increment nr_preds.
Increment 'i' when matching '||' or '&&' so the entire two-character
operator is skipped.
Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 3bd0b47a627f..bcd37fd6c109 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2208,6 +2208,7 @@ static int calc_stack(const char *str, int *parens, int *preds, int *err)
case '&':
if (str[i+1] != str[i])
break;
+ i++;
is_pred = false;
continue;
case '(':
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v2 6/9] tracing/filters: Skip second character of '&&' and '||' in calc_stack()
2026-10-25 8:38 ` [PATCH v2 6/9] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
@ 2026-10-26 20:47 ` Steven Rostedt
0 siblings, 0 replies; 13+ messages in thread
From: Steven Rostedt @ 2026-10-26 20:47 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: Shuah Khan, Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest
On Sun, 25 Oct 2026 17:38:05 +0900
"Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
> From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
>
> In calc_stack(), when encountering '||' or '&&' (where str[i+1] == str[i]),
> the loop sets 'is_pred = false' and continues without advancing 'i' past
> the second character of the operator. On the next iteration, the second '|'
> or '&' sees str[i+1] != str[i], breaks out of the switch statement, and is
> counted as the start of a new predicate (incrementing nr_preds and setting
> is_pred = true).
>
> Consequently, a trailing logical operator (e.g. 'common_pid == 1 ||') is
> counted as having 2 predicates instead of 1, and any subsequent non-space
> character before a delimiter fails to increment nr_preds.
"common_pid ||' is an invalid expression.
>
> Increment 'i' when matching '||' or '&&' so the entire two-character
> operator is skipped.
This is just wrong.
Masami, don't blindly trust AI.
I purposely did it this way. Because && and || creates a new predicate.
If you have "x||y"
^
i
Start with i = 0; is_pred = false; nr_preds = 0;
str[i] == 'x'
if (!is_pred) {
nr_preds++; // nr_preds = 1
is_pred = true;
}
in for loop i++; // i = 1; str[i] = '|'
switch (str[i])
case '|':
if (str[i+1] != str[i]) // false
is_pred = false;
continue;
in for loop i++; // i = 2; str[i] == '|';
switch (str[i])
case '|':
if (str[i+1] != str[i]) // true
break;
if (!is_pred) {
nr_preds++; // nr_preds = 2
is_pred = true;
}
in for loop i++; // i = 3; str[i] == 'y';
if (!is_pred) { // false because is_pred == true
Loop ends.
nr_preds == 2. This is correct.
This patch is garbage.
-- Stevwe
>
> Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
> Cc: stable@vger.kernel.org
> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> ---
> kernel/trace/trace_events_filter.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
> index 3bd0b47a627f..bcd37fd6c109 100644
> --- a/kernel/trace/trace_events_filter.c
> +++ b/kernel/trace/trace_events_filter.c
> @@ -2208,6 +2208,7 @@ static int calc_stack(const char *str, int *parens, int *preds, int *err)
> case '&':
> if (str[i+1] != str[i])
> break;
> + i++;
> is_pred = false;
> continue;
> case '(':
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 7/9] tracing/filters: Reject unsupported field sizes in parse_pred()
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (5 preceding siblings ...)
2026-10-25 8:38 ` [PATCH v2 6/9] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
@ 2026-10-25 8:38 ` Masami Hiramatsu (Google)
2026-10-25 8:38 ` [PATCH v2 8/9] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
2026-10-25 8:38 ` [PATCH v2 9/9] selftests/ftrace: Add event filter syntax check test case Masami Hiramatsu (Google)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:38 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Prior to commit 80765597bc58 ("tracing: Rewrite filter logic to be simpler
and faster"), parse_pred() checked whether select_comparison_fn() returned
NULL and failed with FILT_ERR_INVALID_OP and -EINVAL. That check was lost
during the rewrite, and commit 3cbec9d7b93d ("tracing/filters: Enable
filtering a scalar field by a cpumask") similarly omitted a default error
case when matching field->size for CPUS{} comparisons.
If a non-string field (FILTER_OTHER) has a size other than 1, 2, 4, or 8
(such as a fixed-size integer array field), pred->fn_num remains
FILTER_PRED_FN_NOP (0) and parse_pred() returns success. Subsequently,
update_preds() rejects FILTER_PRED_FN_NOP via WARN_ON_ONCE() and returns
-EINVAL without setting a parse_error() message, triggering a kernel
warning from user-supplied filter input.
Restore the check for FILTER_PRED_FN_NOP in both the numeric scalar and
CPUS{} branches of parse_pred() and report FILT_ERR_INVALID_OP.
Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Fixes: 3cbec9d7b93d ("tracing/filters: Enable filtering a scalar field by a cpumask")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index bcd37fd6c109..8d7fb6e9d7f8 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1996,6 +1996,10 @@ static int parse_pred(const char *str, void *data,
break;
}
}
+ if (pred->fn_num == FILTER_PRED_FN_NOP) {
+ parse_error(pe, FILT_ERR_INVALID_OP, pos + i);
+ goto err_free;
+ }
/* This is either a string, or an integer */
} else if (str[i] == '\'' || str[i] == '"') {
@@ -2141,6 +2145,10 @@ static int parse_pred(const char *str, void *data,
else {
pred->fn_num = select_comparison_fn(pred->op, field->size,
field->is_signed);
+ if (pred->fn_num == FILTER_PRED_FN_NOP) {
+ parse_error(pe, FILT_ERR_INVALID_OP, pos + i);
+ goto err_free;
+ }
if (pred->op == OP_NE)
pred->not = 1;
}
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH v2 8/9] tracing/filters: Terminate filter string at first newline
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (6 preceding siblings ...)
2026-10-25 8:38 ` [PATCH v2 7/9] tracing/filters: Reject unsupported field sizes in parse_pred() Masami Hiramatsu (Google)
@ 2026-10-25 8:38 ` Masami Hiramatsu (Google)
2026-10-26 20:49 ` Steven Rostedt
2026-10-25 8:38 ` [PATCH v2 9/9] selftests/ftrace: Add event filter syntax check test case Masami Hiramatsu (Google)
8 siblings, 1 reply; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:38 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Each trace event or subsystem only holds a single filter, and
append_filter_err() / tracing_log_err() caret placement assumes a
single-line filter string. However, apply_event_filter() and
apply_subsystem_event_filter() only call strstrip(), which strips
trailing newlines but leaves embedded newlines intact.
An embedded newline in a filter string (for example inside 'CPUS{0\n"}')
causes cpulist_parse() / bitmap_parselist() to treat '\n' as the end of
the cpulist and ignore trailing characters before '}', desynchronizing
quote tracking between calc_stack() and parse_pred() and misaligning
error_log caret reporting.
Terminate filter_string at the first '\n' in apply_event_filter(),
apply_subsystem_event_filter(), and create_filter_start() so only the
first line is parsed.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
kernel/trace/trace_events_filter.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 8d7fb6e9d7f8..14afcca7a444 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2426,10 +2426,15 @@ static int create_filter_start(char *filter_string, bool set_str,
struct event_filter *filter;
struct filter_parse_error *pe = NULL;
int err = 0;
+ char *p;
if (WARN_ON_ONCE(*pse || *filterp))
return -EINVAL;
+ p = strchr(filter_string, '\n');
+ if (p)
+ *p = '\0';
+
filter = kzalloc_obj(*filter);
if (filter && set_str) {
filter->filter_string = kstrdup(filter_string, GFP_KERNEL);
@@ -2543,11 +2548,16 @@ int apply_event_filter(struct trace_event_file *file, char *filter_string)
{
struct trace_event_call *call = file->event_call;
struct event_filter *filter = NULL;
+ char *p;
int err;
if (file->flags & EVENT_FILE_FL_FREED)
return -ENODEV;
+ p = strchr(filter_string, '\n');
+ if (p)
+ *p = '\0';
+
if (!strcmp(strstrip(filter_string), "0")) {
filter_disable(file);
filter = event_filter(file);
@@ -2595,6 +2605,7 @@ int apply_subsystem_event_filter(struct trace_subsystem_dir *dir,
struct trace_array *tr = dir->tr;
struct event_filter *filter = NULL;
int err = 0;
+ char *p;
guard(mutex)(&event_mutex);
@@ -2602,6 +2613,10 @@ int apply_subsystem_event_filter(struct trace_subsystem_dir *dir,
if (!dir->nr_events)
return -ENODEV;
+ p = strchr(filter_string, '\n');
+ if (p)
+ *p = '\0';
+
if (!strcmp(strstrip(filter_string), "0")) {
filter_free_subsystem_preds(dir, tr);
remove_filter_string(system->filter);
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v2 8/9] tracing/filters: Terminate filter string at first newline
2026-10-25 8:38 ` [PATCH v2 8/9] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
@ 2026-10-26 20:49 ` Steven Rostedt
0 siblings, 0 replies; 13+ messages in thread
From: Steven Rostedt @ 2026-10-26 20:49 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: Shuah Khan, Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest
On Sun, 25 Oct 2026 17:38:22 +0900
"Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
> From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
>
> Each trace event or subsystem only holds a single filter, and
> append_filter_err() / tracing_log_err() caret placement assumes a
> single-line filter string. However, apply_event_filter() and
> apply_subsystem_event_filter() only call strstrip(), which strips
> trailing newlines but leaves embedded newlines intact.
>
> An embedded newline in a filter string (for example inside 'CPUS{0\n"}')
> causes cpulist_parse() / bitmap_parselist() to treat '\n' as the end of
> the cpulist and ignore trailing characters before '}', desynchronizing
> quote tracking between calc_stack() and parse_pred() and misaligning
> error_log caret reporting.
>
> Terminate filter_string at the first '\n' in apply_event_filter(),
> apply_subsystem_event_filter(), and create_filter_start() so only the
> first line is parsed.
How about also checking if there's any non white space after the newline,
and if so, fail the filter outright with -EINVAL?
-- Steve
>
> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> ---
> kernel/trace/trace_events_filter.c | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
> index 8d7fb6e9d7f8..14afcca7a444 100644
> --- a/kernel/trace/trace_events_filter.c
> +++ b/kernel/trace/trace_events_filter.c
> @@ -2426,10 +2426,15 @@ static int create_filter_start(char *filter_string, bool set_str,
> struct event_filter *filter;
> struct filter_parse_error *pe = NULL;
> int err = 0;
> + char *p;
>
> if (WARN_ON_ONCE(*pse || *filterp))
> return -EINVAL;
>
> + p = strchr(filter_string, '\n');
> + if (p)
> + *p = '\0';
> +
> filter = kzalloc_obj(*filter);
> if (filter && set_str) {
> filter->filter_string = kstrdup(filter_string, GFP_KERNEL);
> @@ -2543,11 +2548,16 @@ int apply_event_filter(struct trace_event_file *file, char *filter_string)
> {
> struct trace_event_call *call = file->event_call;
> struct event_filter *filter = NULL;
> + char *p;
> int err;
>
> if (file->flags & EVENT_FILE_FL_FREED)
> return -ENODEV;
>
> + p = strchr(filter_string, '\n');
> + if (p)
> + *p = '\0';
> +
> if (!strcmp(strstrip(filter_string), "0")) {
> filter_disable(file);
> filter = event_filter(file);
> @@ -2595,6 +2605,7 @@ int apply_subsystem_event_filter(struct trace_subsystem_dir *dir,
> struct trace_array *tr = dir->tr;
> struct event_filter *filter = NULL;
> int err = 0;
> + char *p;
>
> guard(mutex)(&event_mutex);
>
> @@ -2602,6 +2613,10 @@ int apply_subsystem_event_filter(struct trace_subsystem_dir *dir,
> if (!dir->nr_events)
> return -ENODEV;
>
> + p = strchr(filter_string, '\n');
> + if (p)
> + *p = '\0';
> +
> if (!strcmp(strstrip(filter_string), "0")) {
> filter_free_subsystem_preds(dir, tr);
> remove_filter_string(system->filter);
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 9/9] selftests/ftrace: Add event filter syntax check test case
2026-10-25 8:37 [PATCH v2 0/9] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
` (7 preceding siblings ...)
2026-10-25 8:38 ` [PATCH v2 8/9] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
@ 2026-10-25 8:38 ` Masami Hiramatsu (Google)
8 siblings, 0 replies; 13+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-25 8:38 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a new ftracetest test case (filter-syntax-check.tc) to verify valid
event filter expressions, error_log caret reporting for malformed filter
expressions (empty filters, missing fields, unbalanced parentheses and
quotes, dangling logical operators, invalid fields/values/cpulists),
newline truncation on multi-line filter input, and error returns on
invalid subsystem filters.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
.../ftrace/test.d/filter/filter-syntax-check.tc | 52 ++++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/filter/filter-syntax-check.tc
diff --git a/tools/testing/selftests/ftrace/test.d/filter/filter-syntax-check.tc b/tools/testing/selftests/ftrace/test.d/filter/filter-syntax-check.tc
new file mode 100644
index 000000000000..68c8accc5ffd
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/filter/filter-syntax-check.tc
@@ -0,0 +1,52 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: event filter - test filter syntax error check
+# requires: set_event events/sched/sched_switch/filter error_log
+# flags: instance
+
+check_error() { # command-with-error-pos-by-^
+ ftrace_errlog_check 'event filter parse error' "$1" 'events/sched/sched_switch/filter'
+}
+
+# Valid filter expressions
+echo '(common_pid == 0)' > events/sched/sched_switch/filter
+echo '((common_pid == 0) && (prev_pid == 0))' > events/sched/sched_switch/filter
+echo 'prev_comm ~ "*sh"' > events/sched/sched_switch/filter
+echo 'common_pid == CPUS{0}' > events/sched/sched_switch/filter
+echo 'common_pid != CPUS{0}' > events/sched/sched_switch/filter
+echo 0 > events/sched/sched_switch/filter
+
+# Empty or meaningless filter expressions
+check_error '^()' # NO_FILTER
+check_error '=^= 1' # INVALID_FILTER
+
+# Unbalanced parentheses and quotes
+check_error '^(common_pid == 1' # TOO_MANY_OPEN
+check_error 'common_pid == 1^)' # TOO_MANY_CLOSE
+check_error 'prev_comm == ^"bash' # MISSING_QUOTE
+
+# Dangling logical operators
+check_error 'common_pid == 1 &&^' # TOO_MANY_PREDS
+check_error 'common_pid == 1 ||^' # TOO_MANY_PREDS
+check_error 'common_pid == 1 || (common_pid == 2 && )^' # INVALID_FILTER
+
+# Invalid field, operator, or operand value
+check_error 'no_such_field ^== 1' # FIELD_NOT_FOUND
+check_error 'common_pid == @^' # INVALID_VALUE
+check_error 'common_pid == CPUS0}^' # MISSING_BRACE_OPEN
+check_error 'common_pid == CPUS{}^' # INVALID_CPULIST
+
+# Multi-line input must be truncated at the first newline
+echo > error_log
+! ( printf "common_pid == CPUS{0\n\"} && common_pid == 0 && common_pid == CPUS{0\n\"}" | \
+ dd of=events/sched/sched_switch/filter bs=4096 ) 2> /dev/null
+grep -q "Missing '}'" error_log
+! grep -q "Missing matching quote" error_log
+
+# Subsystem filter must fail on invalid filter expressions
+echo 'common_pid >= 0' > events/sched/filter
+! echo 'no_such_field == 1' > events/sched/filter
+! echo 'common_pid == 1 &&' > events/sched/filter
+echo 0 > events/sched/filter
+
+exit 0
^ permalink raw reply [flat|nested] 13+ messages in thread