Commit a0e7c5bd authored by Steven Rostedt's avatar Steven Rostedt Committed by Li Huafei
Browse files

tracing: Check "%s" dereference via the field and not the TP_printk format

stable inclusion
from stable-v6.6.70
commit f452f397f9a6a605989e4151078ab76b41d490cc
category: bugfix
bugzilla: https://gitee.com/src-openeuler/kernel/issues/IBJ835

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=f452f397f9a6a605989e4151078ab76b41d490cc

--------------------------------

[ Upstream commit afd2627f727b89496d79a6b934a025fc916d4ded ]

The TP_printk() portion of a trace event is executed at the time a event
is read from the trace. This can happen seconds, minutes, hours, days,
months, years possibly later since the event was recorded. If the print
format contains a dereference to a string via "%s", and that string was
allocated, there's a chance that string could be freed before it is read
by the trace file.

To protect against such bugs, there are two functions that verify the
event. The first one is test_event_printk(), which is called when the
event is created. It reads the TP_printk() format as well as its arguments
to make sure nothing may be dereferencing a pointer that was not copied
into the ring buffer along with the event. If it is, it will trigger a
WARN_ON().

For strings that use "%s", it is not so easy. The string may not reside in
the ring buffer but may still be valid. Strings that are static and part
of the kernel proper which will not be freed for the life of the running
system, are safe to dereference. But to know if it is a pointer to a
static string or to something on the heap can not be determined until the
event is triggered.

This brings us to the second function that tests for the bad dereferencing
of strings, trace_check_vprintf(). It would walk through the printf format
looking for "%s", and when it finds it, it would validate that the pointer
is safe to read. If not, it would produces a WARN_ON() as well and write
into the ring buffer "[UNSAFE-MEMORY]".

The problem with this is how it used va_list to have vsnprintf() handle
all the cases that it didn't need to check. Instead of re-implementing
vsnprintf(), it would make a copy of the format up to the %s part, and
call vsnprintf() with the current va_list ap variable, where the ap would
then be ready to point at the string in question.

For architectures that passed va_list by reference this was possible. For
architectures that passed it by copy it was not. A test_can_verify()
function was used to differentiate between the two, and if it wasn't
possible, it would disable it.

Even for architectures where this was feasible, it was a stretch to rely
on such a method that is undocumented, and could cause issues later on
with new optimizations of the compiler.

Instead, the first function test_event_printk() was updated to look at
"%s" as well. If the "%s" argument is a pointer outside the event in the
ring buffer, it would find the field type of the event that is the problem
and mark the structure with a new flag called "needs_test". The event
itself will be marked by TRACE_EVENT_FL_TEST_STR to let it be known that
this event has a field that needs to be verified before the event can be
printed using the printf format.

When the event fields are created from the field type structure, the
fields would copy the field type's "needs_test" value.

Finally, before being printed, a new function ignore_event() is called
which will check if the event has the TEST_STR flag set (if not, it
returns false). If the flag is set, it then iterates through the events
fields looking for the ones that have the "needs_test" flag set.

Then it uses the offset field from the field structure to find the pointer
in the ring buffer event. It runs the tests to make sure that pointer is
safe to print and if not, it triggers the WARN_ON() and also adds to the
trace output that the event in question has an unsafe memory access.

The ignore_event() makes the trace_check_vprintf() obsolete so it is
removed.

Link: https://lore.kernel.org/all/CAHk-=wh3uOnqnZPpR0PeLZZtyWbZLboZ7cHLCKRWsocvs9Y7hQ@mail.gmail.com/

Cc: stable@vger.kernel.org
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Al Viro <viro@ZenIV.linux.org.uk>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Link: https://lore.kernel.org/20241217024720.848621576@goodmis.org


Fixes: 5013f454 ("tracing: Add check of trace event print fmts for dereferencing pointers")
Signed-off-by: default avatarSteven Rostedt (Google) <rostedt@goodmis.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
Conflicts:
	kernel/trace/trace.h
	kernel/trace/trace.c
[ The conflict in trace.h is caused by the KABI reserved fields.
  The conflicts in trace.c are as follows:
   1. Since the commit dcc4e5728eea has not been merged, it replaces
show_buffer() with the newly introduced seq_buf_str(). However, the
current patch will change seq_buf_str() to trace_seq_printf(), so the
conflict has no actual impact.
   2. Since the commit 07714b4bb3f9 has not been merged, the logic
introduced by this patch will be removed in the current patch, which
directly resolves the conflict without further discussion.
   3. show_buffer() is no longer used and will be removed. ]
Signed-off-by: default avatarLi Huafei <lihuafei1@huawei.com>
parent e835a628
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -280,7 +280,8 @@ struct trace_event_fields {
			const char *name;
			const int  size;
			const int  align;
			const int  is_signed;
			const unsigned int is_signed:1;
			unsigned int needs_test:1;
			const int  filter_type;
			const int  len;
		};
@@ -332,6 +333,7 @@ enum {
	TRACE_EVENT_FL_EPROBE_BIT,
	TRACE_EVENT_FL_FPROBE_BIT,
	TRACE_EVENT_FL_CUSTOM_BIT,
	TRACE_EVENT_FL_TEST_STR_BIT,
};

/*
@@ -349,6 +351,7 @@ enum {
 *  CUSTOM        - Event is a custom event (to be attached to an exsiting tracepoint)
 *                   This is set when the custom event has not been attached
 *                   to a tracepoint yet, then it is cleared when it is.
 *  TEST_STR      - The event has a "%s" that points to a string outside the event
 */
enum {
	TRACE_EVENT_FL_FILTERED		= (1 << TRACE_EVENT_FL_FILTERED_BIT),
@@ -362,6 +365,7 @@ enum {
	TRACE_EVENT_FL_EPROBE		= (1 << TRACE_EVENT_FL_EPROBE_BIT),
	TRACE_EVENT_FL_FPROBE		= (1 << TRACE_EVENT_FL_FPROBE_BIT),
	TRACE_EVENT_FL_CUSTOM		= (1 << TRACE_EVENT_FL_CUSTOM_BIT),
	TRACE_EVENT_FL_TEST_STR		= (1 << TRACE_EVENT_FL_TEST_STR_BIT),
};

#define TRACE_EVENT_FL_UKPROBE (TRACE_EVENT_FL_KPROBE | TRACE_EVENT_FL_UPROBE)
+56 −167
Original line number Diff line number Diff line
@@ -3760,17 +3760,12 @@ char *trace_iter_expand_format(struct trace_iterator *iter)
}

/* Returns true if the string is safe to dereference from an event */
static bool trace_safe_str(struct trace_iterator *iter, const char *str,
			   bool star, int len)
static bool trace_safe_str(struct trace_iterator *iter, const char *str)
{
	unsigned long addr = (unsigned long)str;
	struct trace_event *trace_event;
	struct trace_event_call *event;

	/* Ignore strings with no length */
	if (star && !len)
		return true;

	/* OK if part of the event data */
	if ((addr >= (unsigned long)iter->ent) &&
	    (addr < (unsigned long)iter->ent + iter->ent_size))
@@ -3810,142 +3805,69 @@ static bool trace_safe_str(struct trace_iterator *iter, const char *str,
	return false;
}

static const char *show_buffer(struct trace_seq *s)
{
	struct seq_buf *seq = &s->seq;

	seq_buf_terminate(seq);

	return seq->buffer;
}

static DEFINE_STATIC_KEY_FALSE(trace_no_verify);

static int test_can_verify_check(const char *fmt, ...)
{
	char buf[16];
	va_list ap;
	int ret;

	/*
	 * The verifier is dependent on vsnprintf() modifies the va_list
	 * passed to it, where it is sent as a reference. Some architectures
	 * (like x86_32) passes it by value, which means that vsnprintf()
	 * does not modify the va_list passed to it, and the verifier
	 * would then need to be able to understand all the values that
	 * vsnprintf can use. If it is passed by value, then the verifier
	 * is disabled.
	 */
	va_start(ap, fmt);
	vsnprintf(buf, 16, "%d", ap);
	ret = va_arg(ap, int);
	va_end(ap);

	return ret;
}

static void test_can_verify(void)
{
	if (!test_can_verify_check("%d %d", 0, 1)) {
		pr_info("trace event string verifier disabled\n");
		static_branch_inc(&trace_no_verify);
	}
}

/**
 * trace_check_vprintf - Check dereferenced strings while writing to the seq buffer
 * ignore_event - Check dereferenced fields while writing to the seq buffer
 * @iter: The iterator that holds the seq buffer and the event being printed
 * @fmt: The format used to print the event
 * @ap: The va_list holding the data to print from @fmt.
 *
 * This writes the data into the @iter->seq buffer using the data from
 * @fmt and @ap. If the format has a %s, then the source of the string
 * is examined to make sure it is safe to print, otherwise it will
 * warn and print "[UNSAFE MEMORY]" in place of the dereferenced string
 * pointer.
 * At boot up, test_event_printk() will flag any event that dereferences
 * a string with "%s" that does exist in the ring buffer. It may still
 * be valid, as the string may point to a static string in the kernel
 * rodata that never gets freed. But if the string pointer is pointing
 * to something that was allocated, there's a chance that it can be freed
 * by the time the user reads the trace. This would cause a bad memory
 * access by the kernel and possibly crash the system.
 *
 * This function will check if the event has any fields flagged as needing
 * to be checked at runtime and perform those checks.
 *
 * If it is found that a field is unsafe, it will write into the @iter->seq
 * a message stating what was found to be unsafe.
 *
 * @return: true if the event is unsafe and should be ignored,
 *          false otherwise.
 */
void trace_check_vprintf(struct trace_iterator *iter, const char *fmt,
			 va_list ap)
bool ignore_event(struct trace_iterator *iter)
{
	const char *p = fmt;
	const char *str;
	int i, j;

	if (WARN_ON_ONCE(!fmt))
		return;
	struct ftrace_event_field *field;
	struct trace_event *trace_event;
	struct trace_event_call *event;
	struct list_head *head;
	struct trace_seq *seq;
	const void *ptr;

	if (static_branch_unlikely(&trace_no_verify))
		goto print;
	trace_event = ftrace_find_event(iter->ent->type);

	/* Don't bother checking when doing a ftrace_dump() */
	if (iter->fmt == static_fmt_buf)
		goto print;
	seq = &iter->seq;

	while (*p) {
		bool star = false;
		int len = 0;
	if (!trace_event) {
		trace_seq_printf(seq, "EVENT ID %d NOT FOUND?\n", iter->ent->type);
		return true;
	}

		j = 0;
	event = container_of(trace_event, struct trace_event_call, event);
	if (!(event->flags & TRACE_EVENT_FL_TEST_STR))
		return false;

		/* We only care about %s and variants */
		for (i = 0; p[i]; i++) {
			if (i + 1 >= iter->fmt_size) {
				/*
				 * If we can't expand the copy buffer,
				 * just print it.
				 */
				if (!trace_iter_expand_format(iter))
					goto print;
	head = trace_get_fields(event);
	if (!head) {
		trace_seq_printf(seq, "FIELDS FOR EVENT '%s' NOT FOUND?\n",
				 trace_event_name(event));
		return true;
	}

			if (p[i] == '\\' && p[i+1]) {
				i++;
				continue;
			}
			if (p[i] == '%') {
				/* Need to test cases like %08.*s */
				for (j = 1; p[i+j]; j++) {
					if (isdigit(p[i+j]) ||
					    p[i+j] == '.')
						continue;
					if (p[i+j] == '*') {
						star = true;
						continue;
					}
					break;
				}
				if (p[i+j] == 's')
					break;
				star = false;
			}
			j = 0;
		}
		/* If no %s found then just print normally */
		if (!p[i])
			break;
	/* Offsets are from the iter->ent that points to the raw event */
	ptr = iter->ent;

		/* Copy up to the %s, and print that */
		strncpy(iter->fmt, p, i);
		iter->fmt[i] = '\0';
		trace_seq_vprintf(&iter->seq, iter->fmt, ap);
	list_for_each_entry(field, head, link) {
		const char *str;
		bool good;

		/*
		 * If iter->seq is full, the above call no longer guarantees
		 * that ap is in sync with fmt processing, and further calls
		 * to va_arg() can return wrong positional arguments.
		 *
		 * Ensure that ap is no longer used in this case.
		 */
		if (iter->seq.full) {
			p = "";
			break;
		}
		if (!field->needs_test)
			continue;

		if (star)
			len = va_arg(ap, int);
		str = *(const char **)(ptr + field->offset);

		/* The ap now points to the string data of the %s */
		str = va_arg(ap, const char *);
		good = trace_safe_str(iter, str);

		/*
		 * If you hit this warning, it is likely that the
@@ -3956,45 +3878,14 @@ void trace_check_vprintf(struct trace_iterator *iter, const char *fmt,
		 * instead. See samples/trace_events/trace-events-sample.h
		 * for reference.
		 */
		if (WARN_ONCE(!trace_safe_str(iter, str, star, len),
			      "fmt: '%s' current_buffer: '%s'",
			      fmt, show_buffer(&iter->seq))) {
			int ret;

			/* Try to safely read the string */
			if (star) {
				if (len + 1 > iter->fmt_size)
					len = iter->fmt_size - 1;
				if (len < 0)
					len = 0;
				ret = copy_from_kernel_nofault(iter->fmt, str, len);
				iter->fmt[len] = 0;
				star = false;
			} else {
				ret = strncpy_from_kernel_nofault(iter->fmt, str,
								  iter->fmt_size);
		if (WARN_ONCE(!good, "event '%s' has unsafe pointer field '%s'",
			      trace_event_name(event), field->name)) {
			trace_seq_printf(seq, "EVENT %s: HAS UNSAFE POINTER FIELD '%s'\n",
					 trace_event_name(event), field->name);
			return true;
		}
			if (ret < 0)
				trace_seq_printf(&iter->seq, "(0x%px)", str);
			else
				trace_seq_printf(&iter->seq, "(0x%px:%s)",
						 str, iter->fmt);
			str = "[UNSAFE-MEMORY]";
			strcpy(iter->fmt, "%s");
		} else {
			strncpy(iter->fmt, p + i, j + 1);
			iter->fmt[j+1] = '\0';
	}
		if (star)
			trace_seq_printf(&iter->seq, iter->fmt, len, str);
		else
			trace_seq_printf(&iter->seq, iter->fmt, str);

		p += i + j + 1;
	}
 print:
	if (*p)
		trace_seq_vprintf(&iter->seq, p, ap);
	return false;
}

const char *trace_event_format(struct trace_iterator *iter, const char *fmt)
@@ -10542,8 +10433,6 @@ __init static int tracer_alloc_buffers(void)

	register_snapshot_cmd();

	test_can_verify();

	return 0;

out_free_pipe_cpumask:
+3 −3
Original line number Diff line number Diff line
@@ -648,9 +648,8 @@ void trace_buffer_unlock_commit_nostack(struct trace_buffer *buffer,

bool trace_is_tracepoint_string(const char *str);
const char *trace_event_format(struct trace_iterator *iter, const char *fmt);
void trace_check_vprintf(struct trace_iterator *iter, const char *fmt,
			 va_list ap) __printf(2, 0);
char *trace_iter_expand_format(struct trace_iterator *iter);
bool ignore_event(struct trace_iterator *iter);

int trace_empty(struct trace_iterator *iter);

@@ -1329,7 +1328,8 @@ struct ftrace_event_field {
	int			filter_type;
	int			offset;
	int			size;
	int			is_signed;
	unsigned int		is_signed:1;
	unsigned int		needs_test:1;
	int			len;
	KABI_RESERVE(1)
	KABI_RESERVE(2)
+22 −10
Original line number Diff line number Diff line
@@ -82,7 +82,7 @@ static int system_refcount_dec(struct event_subsystem *system)
	}

static struct ftrace_event_field *
__find_event_field(struct list_head *head, char *name)
__find_event_field(struct list_head *head, const char *name)
{
	struct ftrace_event_field *field;

@@ -114,7 +114,8 @@ trace_find_event_field(struct trace_event_call *call, char *name)

static int __trace_define_field(struct list_head *head, const char *type,
				const char *name, int offset, int size,
				int is_signed, int filter_type, int len)
				int is_signed, int filter_type, int len,
				int need_test)
{
	struct ftrace_event_field *field;

@@ -133,6 +134,7 @@ static int __trace_define_field(struct list_head *head, const char *type,
	field->offset = offset;
	field->size = size;
	field->is_signed = is_signed;
	field->needs_test = need_test;
	field->len = len;

	list_add(&field->link, head);
@@ -151,13 +153,13 @@ int trace_define_field(struct trace_event_call *call, const char *type,

	head = trace_get_fields(call);
	return __trace_define_field(head, type, name, offset, size,
				    is_signed, filter_type, 0);
				    is_signed, filter_type, 0, 0);
}
EXPORT_SYMBOL_GPL(trace_define_field);

static int trace_define_field_ext(struct trace_event_call *call, const char *type,
		       const char *name, int offset, int size, int is_signed,
		       int filter_type, int len)
		       int filter_type, int len, int need_test)
{
	struct list_head *head;

@@ -166,13 +168,13 @@ static int trace_define_field_ext(struct trace_event_call *call, const char *typ

	head = trace_get_fields(call);
	return __trace_define_field(head, type, name, offset, size,
				    is_signed, filter_type, len);
				    is_signed, filter_type, len, need_test);
}

#define __generic_field(type, item, filter_type)			\
	ret = __trace_define_field(&ftrace_generic_fields, #type,	\
				   #item, 0, 0, is_signed_type(type),	\
				   filter_type, 0);			\
				   filter_type, 0, 0);			\
	if (ret)							\
		return ret;

@@ -181,7 +183,8 @@ static int trace_define_field_ext(struct trace_event_call *call, const char *typ
				   "common_" #item,			\
				   offsetof(typeof(ent), item),		\
				   sizeof(ent.item),			\
				   is_signed_type(type), FILTER_OTHER, 0);	\
				   is_signed_type(type), FILTER_OTHER,	\
				   0, 0);				\
	if (ret)							\
		return ret;

@@ -332,6 +335,7 @@ static bool process_pointer(const char *fmt, int len, struct trace_event_call *c
/* Return true if the string is safe */
static bool process_string(const char *fmt, int len, struct trace_event_call *call)
{
	struct trace_event_fields *field;
	const char *r, *e, *s;

	e = fmt + len;
@@ -372,8 +376,16 @@ static bool process_string(const char *fmt, int len, struct trace_event_call *ca
	if (process_pointer(fmt, len, call))
		return true;

	/* Make sure the field is found, and consider it OK for now if it is */
	return find_event_field(fmt, call) != NULL;
	/* Make sure the field is found */
	field = find_event_field(fmt, call);
	if (!field)
		return false;

	/* Test this field's string before printing the event */
	call->flags |= TRACE_EVENT_FL_TEST_STR;
	field->needs_test = 1;

	return true;
}

/*
@@ -2552,7 +2564,7 @@ event_define_fields(struct trace_event_call *call)
			ret = trace_define_field_ext(call, field->type, field->name,
						 offset, field->size,
						 field->is_signed, field->filter_type,
						 field->len);
						 field->len, field->needs_test);
			if (WARN_ON_ONCE(ret)) {
				pr_err("error code is %d\n", ret);
				break;
+5 −1
Original line number Diff line number Diff line
@@ -317,10 +317,14 @@ EXPORT_SYMBOL(trace_raw_output_prep);

void trace_event_printf(struct trace_iterator *iter, const char *fmt, ...)
{
	struct trace_seq *s = &iter->seq;
	va_list ap;

	if (ignore_event(iter))
		return;

	va_start(ap, fmt);
	trace_check_vprintf(iter, trace_event_format(iter, fmt), ap);
	trace_seq_vprintf(s, trace_event_format(iter, fmt), ap);
	va_end(ap);
}
EXPORT_SYMBOL(trace_event_printf);