[PATCH v2 1/1] perf/core: Restrict function predicates in trace event filters

From: Zhengchuan Liang

Date: Thu Oct 01 2026 - 19:47:05 EST


Count-only perf tracepoint events can be opened without tracepoint
permission because they do not expose raw sample data. Their
PERF_EVENT_IOC_SET_FILTER ioctl can still pass filter expressions to the
tracing parser.

A .function operand makes the parser resolve either a numeric kernel
address or a kernel symbol. The ioctl return value can therefore be used
as an oracle to recover the randomized kernel text base.

Trace events marked TRACE_EVENT_FL_CAP_ANY are different: task-local
events may expose their raw fields to unprivileged users. Syscall
tracepoints and uprobes deliberately use this exception, so denying every
filter without tracepoint permission would break their ordinary filters.

Check perf_allow_tracepoint() in perf. If permission is denied, continue
only for a task-attached TRACE_EVENT_FL_CAP_ANY event whose filter does
not contain an unquoted .function postfix. This rejects the predicate
before the tracing parser can resolve either form of operand. The scan
uses the parser's quote rules, so .function inside a string operand
remains an ordinary filter value.

This preserves ordinary filters on task-local events whose fields are
already available to the caller, while non-CAP_ANY filters and kernel
address resolution remain unavailable without tracepoint permission.

Fixes: e6745a4da964 ("tracing: Add a way to filter function addresses to function names")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Zhengchuan Liang <zcliangcn@xxxxxxxxx>
---
Changes in v2:
- Keep the entire fix in perf, without modifying kernel/trace/*, as
requested by Steven.
- Preserve ordinary filters for task-local TRACE_EVENT_FL_CAP_ANY events
while rejecting their unquoted .function predicates without tracepoint
permission.
- Reject all filters on other trace events without tracepoint permission.
- Follow the tracing parser's quote rules so .function inside a string
operand remains an ordinary value.

v1: https://lore.kernel.org/all/95d3721fa8d4c7a4577ec14e9d7caec4fd0cefcc.1790553331.git.zcliangcn@xxxxxxxxx/

kernel/events/core.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 634d2ccbab82..11db8689ac90 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -12251,6 +12251,33 @@ perf_event_set_addr_filter(struct perf_event *event, char *filter_str)
return ret;
}

+#ifdef CONFIG_EVENT_TRACING
+static bool perf_event_filter_has_function(const char *filter_str)
+{
+ char quote = 0;
+
+ for (; *filter_str; filter_str++) {
+ if (quote) {
+ if (*filter_str == quote)
+ quote = 0;
+ continue;
+ }
+
+ switch (*filter_str) {
+ case '\'':
+ case '"':
+ quote = *filter_str;
+ break;
+ default:
+ if (str_has_prefix(filter_str, ".function"))
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif
+
static int perf_event_set_filter(struct perf_event *event, void __user *arg)
{
int ret = -EINVAL;
@@ -12264,6 +12291,14 @@ static int perf_event_set_filter(struct perf_event *event, void __user *arg)
if (perf_event_is_tracing(event)) {
struct perf_event_context *ctx = event->ctx;

+ ret = perf_allow_tracepoint();
+ if (ret && (!(event->attach_state & PERF_ATTACH_TASK) ||
+ !(event->tp_event->flags & TRACE_EVENT_FL_CAP_ANY) ||
+ perf_event_filter_has_function(filter_str))) {
+ kfree(filter_str);
+ return ret;
+ }
+
/*
* Beware, here be dragons!!
*
--
2.25.1