tracing: Last minute updates for 7.4
Subsystems (3)
cover tracing: Last minute updates for 7.4
git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git
trace/for-next
Head SHA1: 1fdc4164e488916402badddbb1f51e9380dabb91
Haoran Wang (1):
tracing: Guard trace_event_get_offsets() against an empty fields list
Kyle Zeng (1):
tracing: Reject incomplete event filter expressions
Steven Rostedt (1):
cpuidle: haltpoll: Only define guest_halt_poll_ns tracepoint when used
----
include/trace/events/power.h | 3 +++ kernel/trace/trace_events.c | 4 ++++ kernel/trace/trace_events_filter.c | 6 ++++++ .../test.d/filter/event-filter-incomplete.tc | 24 ++++++++++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc
PATCH 1/3 cpuidle: haltpoll: Only define guest_halt_poll_ns tracepoint when used
From: Steven Rostedt <[email protected]>
Tracepoints that are defined take up around 5K each, even if they are not used. When a tracepoint is used only in code when a specific config is enabled, their definition must also be only defined when the same config is enabled otherwise it wastes memory.
The tracepoint guest_halt_poll_ns is only used when CONFIG_CPU_IDLE_GOV_HALTPOLL is enabled. Guard the definition of that tracepoint with the same config.
--- include/trace/events/power.h | 3 +++ 1 file changed, 3 insertions(+)
include/trace/events/power.h +3 -0
@@ -497,6 +497,7 @@ DEFINE_EVENT(dev_pm_qos_request, dev_pm_qos_remove_request,TP_ARGS (name, type, new_value) ); +#ifdef CONFIG_CPU_IDLE_GOV_HALTPOLL TRACE_EVENT(guest_halt_poll_ns, TP_PROTO(bool grow, unsigned int new, unsigned int old),
@@ -525,6 +526,8 @@ TRACE_EVENT(guest_halt_poll_ns, trace_guest_halt_poll_ns(true, new, old)#define trace_guest_halt_poll_ns_shrink (new, old) \ trace_guest_halt_poll_ns(false, new, old) +#endif /* CONFIG_CPU_IDLE_GOV_HALTPOLL */ +#endif /* _TRACE_POWER_H */ /* This part must be outside protection */
Signature · 1 line
2.53.0
PATCH 2/3 tracing: Guard trace_event_get_offsets() against an empty fields list
In reply to Steven RostedtFrom: Haoran Wang <[email protected]>
trace_event_get_offsets() dereferences the first entry of the fields list returned by trace_get_fields() via list_first_entry(). list_first_entry() then returns the list head itself, and tail->offset + tail->size reads past it. The corrupted result is used as the BPF context-size bound in __perf_event_set_bpf_prog():
int off = trace_event_get_offsets(event->tp_event);
if (prog->aux->max_ctx_offset > off)
return -EACCES;so a context-reading BPF program that ought to be rejected can instead be attached to a zero-field tracepoint (which still carries TRACE_EVENT_FL_TRACEPOINT) and read adjacent kernel memory when the tracepoint fires, bypassing the verifier's context-size confinement.
This is reachable through user_events: a user can register an event with no fields (e.g. echo u:my_event > dynamic_events), which leaves user->fields empty while the event remains a BPF-attachable tracepoint, trace_get_fields() then routes through user_event_get_fields() to that empty list. The corrupted
result after list_first_entry is then used as the BPF context-size bound in __perf_event_set_bpf_prog(), which lets context-reading BPF programs attach to a zero-field tracepoint when they should be rejected with -EACCES.
Add list empty check to fix this bug.
--- kernel/trace/trace_events.c | 4 ++++ 1 file changed, 4 insertions(+)
kernel/trace/trace_events.c +4 -0
@@ -246,6 +246,10 @@ int trace_event_get_offsets(struct trace_event_call *call)struct list_head *head; head =trace_get_fields (call); + +if (list_empty (head)) +return sizeof (struct trace_entry ); +/* * head->next points to the last field with the largest offset, * since it was added last by trace_define_field()
Signature · 1 line
2.53.0
PATCH 3/3 tracing: Reject incomplete event filter expressions
In reply to Steven RostedtFrom: Kyle Zeng <[email protected]>
A non-empty expression normally finishes in predicate_parse()'s inner loop, which resolves the pending logical operators before jumping to finalization. If the input ends after an operator, possibly followed by unary '!' tokens, the outer loop instead falls through with unresolved operator state.
For example, "common_pid == 1 || common_pid == 2 &&" leaves prog[0].target equal to -1. The second pass then reads prog[-1] before the forward-jump check can reject it. Depending on adjacent slab contents, it can also copy an out-of-bounds word into a branch target that passes the forward-only check. A self-targeted perf tracepoint counting event can reach this path through PERF_EVENT_IOC_SET_FILTER.
Reject the outer-loop fallthrough when a predicate has already been parsed. This ensures that non-empty expressions only reach finalization after the inner loop has resolved their operators. Keep the existing no-filter error for input that has no predicates. Add a regression test for trailing logical operators and unary negations.
--- kernel/trace/trace_events_filter.c | 6 +++++ .../test.d/filter/event-filter-incomplete.tc | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc
kernel/trace/trace_events_filter.c +6 -0
@@ -598,6 +598,12 @@ predicate_parse(const char *str, int nr_parens, int nr_preds, top--;/* #12 */ } } +/* Only the inner loop can complete a non-empty expression. */ +if (N ) { + ret = -EINVAL ; +parse_error (pe,FILT_ERR_INVALID_FILTER , ptr - str); + goto out_free; + } out:if (top != op_stack) {/* Too many '(' */
tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc new file +24 -0
@@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event filter - reject incomplete logical expressions +# requires: events/sched/sched_switch/filter +# flags: instance + +filter=events/sched/sched_switch/filter + +for base in \ + 'common_pid == 1' \ + 'common_pid == 1 || common_pid == 2' \ + 'common_pid == 1 && common_pid == 2' \ + '!(common_pid == 1 || common_pid == 2)' +do + printf '%s' "$base" > "$filter" + for tail in '&&' '||' '&& !' '|| !' '&& !! ' '|| !! '; do + if printf '%s' "$base $tail" > "$filter" 2>/dev/null; then + echo "Accepted incomplete filter: $base $tail" + exit_fail + fi + done +done + +echo 0 > "$filter"
Signature · 1 line
2.53.0