mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs
@ 2026-10-24 16:25 Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 01/10] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:25 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
  Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, linux-kselftest

Hi,

Here is a series of fixes for various bugs in the trace event filter
parser and predicate evaluation in kernel/trace/trace_events_filter.c,
along with a new ftracetest test case covering filter syntax checks.

Summary of the patches:

- [01/10] Compute the actual string length with strnlen()/strlen() in
  regex_match_end() so suffix matching ("*pattern") works on fixed-size
  string fields (such as comm) and dynamic strings.
- [02/10] Fix process_system_preds() so that "fail" is only cleared when
  at least one event in the subsystem succeeds, and use
  delay_free_filter() when freeing replaced filters on error paths.
- [03/10] Check "cpu >= nr_cpu_ids" before calling cpumask_test_cpu() in
  do_filter_cpumask_scalar() to avoid out-of-bounds bit tests when
  NR_CPUS > nr_cpu_ids.
- [04/10] Return "pred->op == OP_NE" when "cpu >= nr_cpu_ids" in
  DEFINE_CPUMASK_COMPARISON_PRED so "!=" against a cpumask evaluates to
  true for out-of-range CPU numbers.
- [05/10] Ensure parse_error() is called on all -EINVAL error paths in
  parse_pred(), predicate_parse(), and process_preds() instead of
  leaving lasterr as 0 ("Error: (0)").
- [06/10] Fix function event ("ip") operand parsing in parse_pred() to
  avoid reading past the NUL terminator on empty operands, check for
  missing closing quotes, advance past closing quotes, and reject empty
  operands or stray quotes/'(' in unquoted operands.
- [07/10] Advance past the second character of "&&" and "||" in
  calc_stack() so the second operator character is not miscounted as a
  new predicate.
- [08/10] Reject unsupported field sizes in parse_pred() when
  select_comparison_fn() or CPUS{} size matching leaves pred->fn_num as
  FILTER_PRED_FN_NOP, avoiding a kernel warning in update_pred_fn().
- [09/10] Terminate the input filter string at the first newline ('\n')
  in create_filter_start(), apply_event_filter(), and
  apply_subsystem_event_filter() so multi-line input cannot desynchronize
  cpulist_parse()/parse_pred() from calc_stack() or misalign error_log
  caret placement.
- [10/10] Add filter-syntax-check.tc to ftrace selftests to test valid
  filters, syntax error diagnostics and caret positions, multi-line
  input truncation, and subsystem filter error returns.

Note: These bugs were identified and the fixes and cover letter were
prepared with the assistance of an LLM coding assistant.

Also, note that [09/10] may need more discussion because it can break
user space tool which attempts to use line-break characters as a filter
for reasons that are not entirely clear. But as far as I can see, the
error_log does not support such multi-line filter input. So I think
we still need it. (But not sure we should backport it, maybe as an
improvement.)

Thank you,

---
base-commit: 1fdc4164e488916402badddbb1f51e9380dabb91

Masami Hiramatsu (Google) (10):
      tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings
      tracing/filters: Fix false success return in process_system_preds()
      tracing/filters: Check CPU bounds before cpumask_test_cpu() in do_filter_cpumask_scalar()
      tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison
      tracing/filters: Report parse error on all -EINVAL paths in filter parsing
      tracing/filters: Fix function event operand parsing in parse_pred()
      tracing/filters: Skip second character of '&&' and '||' in calc_stack()
      tracing/filters: Reject unsupported field sizes in parse_pred()
      tracing/filters: Terminate filter string at first newline
      selftests/ftrace: Add event filter syntax check test case


 kernel/trace/trace_events_filter.c                 |  100 +++++++++++++++-----
 .../ftrace/test.d/filter/filter-syntax-check.tc    |   52 ++++++++++
 2 files changed, 129 insertions(+), 23 deletions(-)
 create mode 100644 tools/testing/selftests/ftrace/test.d/filter/filter-syntax-check.tc

--
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 01/10] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
@ 2026-10-24 16:25 ` Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 02/10] tracing/filters: Fix false success return in process_system_preds() Masami Hiramatsu (Google)
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:25 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 a dynamic string ending with
'\0'), "len - 1" underflows to -1 and never matches.

Compute the actual string length using strnlen(str, len) when @len is
non-zero, and strlen(str) when @len is zero.

Fixes: a3291c14ecf0 ("tracing/filters: Fix MATCH_END_ONLY filter matching")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 kernel/trace/trace_events_filter.c |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 5e74d2dae40a..18ce3ec00d34 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1075,10 +1075,10 @@ 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 = len ? strnlen(str, len) : strlen(str);
 
-	if (strlen >= r->len &&
-	    memcmp(str + strlen - r->len, r->pattern, r->len) == 0)
+	if (slen >= r->len &&
+	    memcmp(str + slen - r->len, r->pattern, r->len) == 0)
 		return 1;
 	return 0;
 }


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 02/10] tracing/filters: Fix false success return in process_system_preds()
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 01/10] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
@ 2026-10-24 16:25 ` Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 03/10] tracing/filters: Check CPU bounds before cpumask_test_cpu() in do_filter_cpumask_scalar() Masami Hiramatsu (Google)
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:25 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 18ce3ec00d34..c2c35b4de85e 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2332,8 +2332,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);
@@ -2348,8 +2350,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)
@@ -2364,14 +2364,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] 11+ messages in thread

* [PATCH 03/10] tracing/filters: Check CPU bounds before cpumask_test_cpu() in do_filter_cpumask_scalar()
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 01/10] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
  2026-10-24 16:25 ` [PATCH 02/10] tracing/filters: Fix false success return in process_system_preds() Masami Hiramatsu (Google)
@ 2026-10-24 16:25 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 04/10] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison Masami Hiramatsu (Google)
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:25 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(), single-CPU cpumask filters are parsed via cpulist_parse(),
which checks against nr_cpumask_bits (NR_CPUS). When NR_CPUS > nr_cpu_ids,
a user can provide a CPU number where cpu >= nr_cpu_ids.

When evaluating such a predicate in filter_pred_cpumask_cpu(),
do_filter_cpumask_scalar() calls cpumask_test_cpu(cpu, mask) without
checking cpu against nr_cpu_ids. This triggers a WARN_ON_ONCE under
CONFIG_DEBUG_PER_CPU_MAPS and causes an out-of-bounds bit test.

Check "cpu >= nr_cpu_ids" at the start of do_filter_cpumask_scalar(). When
the CPU index is out of bounds, return "op == OP_NE" since an out-of-range
CPU is never present in a valid cpumask.

Fixes: 38c6f6808389 ("tracing/filters: Further optimise scalar vs cpumask comparison")
Cc: stable@vger.kernel.org
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 kernel/trace/trace_events_filter.c |    3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index c2c35b4de85e..69da95f42541 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -695,6 +695,9 @@ do_filter_scalar_cpumask(int op, unsigned int cpu, const struct cpumask *mask)
 static inline int
 do_filter_cpumask_scalar(int op, const struct cpumask *mask, unsigned int cpu)
 {
+	if (cpu >= nr_cpu_ids)
+		return op == OP_NE;
+
 	switch (op) {
 	case OP_EQ:
 		return cpumask_test_cpu(cpu, mask) &&


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 04/10] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (2 preceding siblings ...)
  2026-10-24 16:25 ` [PATCH 03/10] tracing/filters: Check CPU bounds before cpumask_test_cpu() in do_filter_cpumask_scalar() Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 05/10] tracing/filters: Report parse error on all -EINVAL paths in filter parsing Masami Hiramatsu (Google)
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 69da95f42541..d9aeeec4ed91 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -762,7 +762,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] 11+ messages in thread

* [PATCH 05/10] tracing/filters: Report parse error on all -EINVAL paths in filter parsing
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (3 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 04/10] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 06/10] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 d9aeeec4ed91..7e119c7164f0 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;
 		}
@@ -1688,8 +1691,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)
@@ -2277,8 +2282,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] 11+ messages in thread

* [PATCH 06/10] tracing/filters: Fix function event operand parsing in parse_pred()
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (4 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 05/10] tracing/filters: Report parse error on all -EINVAL paths in filter parsing Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 07/10] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 7e119c7164f0..96eac7ccd488 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1833,22 +1833,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] 11+ messages in thread

* [PATCH 07/10] tracing/filters: Skip second character of '&&' and '||' in calc_stack()
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (5 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 06/10] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 08/10] tracing/filters: Reject unsupported field sizes in parse_pred() Masami Hiramatsu (Google)
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 96eac7ccd488..e734d8664444 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2207,6 +2207,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] 11+ messages in thread

* [PATCH 08/10] tracing/filters: Reject unsupported field sizes in parse_pred()
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (6 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 07/10] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 09/10] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 10/10] selftests/ftrace: Add event filter syntax check test case Masami Hiramatsu (Google)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 e734d8664444..89f4e119628f 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1995,6 +1995,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] == '"') {
@@ -2140,6 +2144,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] 11+ messages in thread

* [PATCH 09/10] tracing/filters: Terminate filter string at first newline
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (7 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 08/10] tracing/filters: Reject unsupported field sizes in parse_pred() Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  2026-10-24 16:26 ` [PATCH 10/10] selftests/ftrace: Add event filter syntax check test case Masami Hiramatsu (Google)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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 89f4e119628f..213ed9bdf6f3 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -2425,10 +2425,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);
@@ -2542,11 +2547,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);
@@ -2594,6 +2604,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);
 
@@ -2601,6 +2612,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] 11+ messages in thread

* [PATCH 10/10] selftests/ftrace: Add event filter syntax check test case
  2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
                   ` (8 preceding siblings ...)
  2026-10-24 16:26 ` [PATCH 09/10] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
@ 2026-10-24 16:26 ` Masami Hiramatsu (Google)
  9 siblings, 0 replies; 11+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-10-24 16:26 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] 11+ messages in thread

end of thread, other threads:[~2026-10-24 16:27 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 16:25 [PATCH 00/10] tracing/filters: Fix event filter parsing and matching bugs Masami Hiramatsu (Google)
2026-10-24 16:25 ` [PATCH 01/10] tracing/filters: Fix MATCH_END_ONLY regex matching for fixed-size strings Masami Hiramatsu (Google)
2026-10-24 16:25 ` [PATCH 02/10] tracing/filters: Fix false success return in process_system_preds() Masami Hiramatsu (Google)
2026-10-24 16:25 ` [PATCH 03/10] tracing/filters: Check CPU bounds before cpumask_test_cpu() in do_filter_cpumask_scalar() Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 04/10] tracing/filters: Handle out-of-range CPU for OP_NE in cpumask comparison Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 05/10] tracing/filters: Report parse error on all -EINVAL paths in filter parsing Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 06/10] tracing/filters: Fix function event operand parsing in parse_pred() Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 07/10] tracing/filters: Skip second character of '&&' and '||' in calc_stack() Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 08/10] tracing/filters: Reject unsupported field sizes in parse_pred() Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 09/10] tracing/filters: Terminate filter string at first newline Masami Hiramatsu (Google)
2026-10-24 16:26 ` [PATCH 10/10] selftests/ftrace: Add event filter syntax check test case Masami Hiramatsu (Google)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®