predicate_parse() seeds every new program entry with a backwards link:

        prog[N].target = N-1;

so prog[0].target starts out as -1. It is expected to be overwritten by
update_preds() before the second pass walks the program.

If the expression ends right after "&&" or "||", the pending PROCESS_AND
or PROCESS_OR bit on the operator stack is never consumed, update_preds()
never runs, and prog[0].target is still -1 when the second pass starts:

        for (i = N-1 ; i--; ) {
                int target = prog[i].target;
                if (prog[i].when_to_branch == prog[target].when_to_branch)
                        prog[i].target = prog[target].target;
        }

That reads prog[-1], 16 bytes in front of the kcalloc()ed program. The
value read there is then stored back as a branch target, and the only
check on a branch target is the lower bound in the third pass:

        if (WARN_ON(prog[i].target <= i))

so any positive value is accepted, and filter_match_preds() goes on to
use it as an unchecked index into prog[].

Reproduced on 6.12.112 with KASAN as uid 1000, with perf_event_paranoid=2
and without tracefs. One perf_event_open() and one ioctl() are enough:

        a.type = PERF_TYPE_TRACEPOINT;
        a.config = <any tracepoint id>;
        a.exclude_kernel = 1;
        fd = perf_event_open(&a, 0, -1, -1, 0);
        ioctl(fd, PERF_EVENT_IOC_SET_FILTER, "common_pid==1||common_pid==1&&");

        BUG: KASAN: slab-out-of-bounds in process_preds+0x151e/0x1b60
        Read of size 4 at addr ffff8881014ad474 by task exp_progm1/123
        CPU: 1 UID: 1000 PID: 123 Comm: exp_progm1 Not tainted 6.12.112-dirty #1
        Call Trace:
         process_preds
         create_filter
         ftrace_profile_set_filter
         _perf_ioctl
         perf_ioctl
         __x64_sys_ioctl

The faulting address is prog - 12, that is prog[-1].when_to_branch. On a
kernel built with CONFIG_SLAB_VIRTUAL the same read lands on the unmapped
guard page in front of the slab whenever the program happens to be the
first object in it, and that is a fatal page fault rather than a silent
read.

An expression is not supposed to end on an operator, and that is the only
way a PROCESS_AND or PROCESS_OR bit survives to the end of the parse, so
reject it there.

Fixes: 80765597bc58 ("tracing: Rewrite filter logic to be simpler and faster")
Cc: [email protected]
Signed-off-by: JongHyuk Kim (MalHyuk) <[email protected]>
---
 kernel/trace/trace_events_filter.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/kernel/trace/trace_events_filter.c 
b/kernel/trace/trace_events_filter.c
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -602,6 +602,13 @@
                goto out_free;
        }
 
+       if (*top & (PROCESS_AND | PROCESS_OR)) {
+               /* Expression ends on an operator */
+               ret = -EINVAL;
+               parse_error(pe, FILT_ERR_INVALID_OP, ptr - str);
+               goto out_free;
+       }
+
        if (!N) {
                /* No program? */
                ret = -EINVAL;

Reply via email to