From: Kyle Zeng <kylebot@openai.com>
To: linux-trace-kernel@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, rostedt@goodmis.org,
mhiramat@kernel.org, outbounddisclosures@openai.com,
Kyle Zeng <kylebot@openai.com>,
stable@vger.kernel.org
Subject: [PATCH] tracing/filters: Check perf permissions before resolving .function
Date: Tue, 6 Oct 2026 15:51:12 -0700 [thread overview]
Message-ID: <20261006225112.53503-1-kylebot@openai.com> (raw)
perf_trace_event_perm() allows tracepoint counters that do not request
PERF_SAMPLE_RAW without raw tracepoint permissions. A self-targeted,
disabled event with exclude_kernel=1 can therefore reach the filter
compiler even at perf_event_paranoid=2.
The .function suffix accepts any field of sizeof(long) and resolves its
operand through kallsyms_lookup_name() and kallsyms_lookup_size_offset().
The success or failure of a numeric filter discloses whether an address
belongs to a known kernel symbol range. On x86-64 this can be used to
recover the randomized kernel image base. A named filter also exposes
the resolved symbol range through the counter when the tracepoint field
is controlled by the caller, as with a syscall argument.
Pass the filter's perf origin to the predicate parser and require
perf_allow_tracepoint() before resolving a .function operand. This uses
the same sysctl, initial-namespace capability and LSM policy as raw
tracepoint access, and closes both the numeric and named-symbol oracles.
Do not change ordinary perf counting filters or filters created through
the separately controlled tracefs interfaces.
Fixes: e6745a4da964 ("tracing: Add a way to filter function addresses to function names")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-6-astra
Signed-off-by: Kyle Zeng <kylebot@openai.com>
---
kernel/trace/trace_events_filter.c | 39 ++++++++++++++++++++++--------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 2b46ca536045..0f8c2054f4a7 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -1625,12 +1625,18 @@ static int filter_pred_fn_call(struct filter_pred *pred, void *event)
}
}
+struct filter_parse_data {
+ struct trace_event_call *call;
+ bool is_perf;
+};
+
/* Called when a predicate is encountered by predicate_parse() */
static int parse_pred(const char *str, void *data,
int pos, struct filter_parse_error *pe,
struct filter_pred **pred_ptr)
{
- struct trace_event_call *call = data;
+ struct filter_parse_data *pdata = data;
+ struct trace_event_call *call = pdata->call;
struct ftrace_event_field *field;
struct filter_pred *pred = NULL;
unsigned long offset;
@@ -1683,6 +1689,14 @@ static int parse_pred(const char *str, void *data,
/* See if the field is a kernel function name */
if ((len = str_has_prefix(str + i, ".function"))) {
+ /* Even counting filters can disclose kernel addresses. */
+ if (pdata->is_perf) {
+ ret = perf_allow_tracepoint();
+ if (ret) {
+ parse_error(pe, ret, pos + i);
+ return ret;
+ }
+ }
function = true;
i += len;
}
@@ -2205,8 +2219,12 @@ static int calc_stack(const char *str, int *parens, int *preds, int *err)
static int process_preds(struct trace_event_call *call,
const char *filter_string,
struct event_filter *filter,
- struct filter_parse_error *pe)
+ struct filter_parse_error *pe, bool is_perf)
{
+ struct filter_parse_data data = {
+ .call = call,
+ .is_perf = is_perf,
+ };
struct prog_entry *prog;
int nr_parens;
int nr_preds;
@@ -2232,7 +2250,7 @@ static int process_preds(struct trace_event_call *call,
return -EINVAL;
prog = predicate_parse(filter_string, nr_parens, nr_preds,
- parse_pred, call, pe);
+ parse_pred, &data, pe);
if (IS_ERR(prog))
return PTR_ERR(prog);
@@ -2281,7 +2299,7 @@ static int process_system_preds(struct trace_subsystem_dir *dir,
if (!filter->filter_string)
goto fail_mem;
- err = process_preds(file->event_call, filter_string, filter, pe);
+ err = process_preds(file->event_call, filter_string, filter, pe, false);
if (err) {
filter_disable(file);
parse_error(pe, FILT_ERR_BAD_SUBSYS_FILTER, 0);
@@ -2377,6 +2395,7 @@ static void create_filter_finish(struct filter_parse_error *pe)
* @call: trace_event_call to create a filter for
* @filter_string: filter string
* @set_str: remember @filter_str and enable detailed error in filter
+ * @is_perf: filter is being created for a perf event
* @filterp: out param for created filter (always updated on return)
* Must be a pointer that references a NULL pointer.
*
@@ -2391,7 +2410,7 @@ static void create_filter_finish(struct filter_parse_error *pe)
*/
static int create_filter(struct trace_array *tr,
struct trace_event_call *call,
- char *filter_string, bool set_str,
+ char *filter_string, bool set_str, bool is_perf,
struct event_filter **filterp)
{
struct filter_parse_error *pe = NULL;
@@ -2405,7 +2424,7 @@ static int create_filter(struct trace_array *tr,
if (err)
return err;
- err = process_preds(call, filter_string, *filterp, pe);
+ err = process_preds(call, filter_string, *filterp, pe, is_perf);
if (err && set_str)
append_filter_err(tr, pe, *filterp);
create_filter_finish(pe);
@@ -2418,7 +2437,7 @@ int create_event_filter(struct trace_array *tr,
char *filter_str, bool set_str,
struct event_filter **filterp)
{
- return create_filter(tr, call, filter_str, set_str, filterp);
+ return create_filter(tr, call, filter_str, set_str, false, filterp);
}
/**
@@ -2476,7 +2495,7 @@ int apply_event_filter(struct trace_event_file *file, char *filter_string)
return 0;
}
- err = create_filter(file->tr, call, filter_string, true, &filter);
+ err = create_filter(file->tr, call, filter_string, true, false, &filter);
/*
* Always swap the call filter with the new filter
@@ -2721,7 +2740,7 @@ int ftrace_profile_set_filter(struct perf_event *event, int event_id,
if (event->filter)
return -EEXIST;
- err = create_filter(NULL, call, filter_str, false, &filter);
+ err = create_filter(NULL, call, filter_str, false, true, &filter);
if (err)
goto free_filter;
@@ -2868,7 +2887,7 @@ static __init int ftrace_test_event_filter(void)
int err;
err = create_filter(NULL, &event_ftrace_test_filter,
- d->filter, false, &filter);
+ d->filter, false, false, &filter);
if (err) {
printk(KERN_INFO
"Failed to get filter for '%s', err %d\n",
base-commit: fd179f8a05be3ccae366b9b96e176b51fbe54aab
next reply other threads:[~2026-10-06 22:51 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 22:51 Kyle Zeng [this message]
2026-10-07 3:44 ` Masami Hiramatsu
2026-10-07 3:47 ` Kyle Zeng
2026-10-07 5:52 ` Masami Hiramatsu
2026-10-07 5:56 ` Kyle Zeng
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261006225112.53503-1-kylebot@openai.com \
--to=kylebot@openai.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mhiramat@kernel.org \
--cc=outbounddisclosures@openai.com \
--cc=rostedt@goodmis.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®