mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 00:35:01 +00:00
tracing fixes for v6.13:
- Replace trace_check_vprintf() with test_event_printk() and ignore_event() The function test_event_printk() checks on boot up if the trace event printf() formats dereference any pointers, and if they do, it then looks at the arguments to make sure that the pointers they dereference will exist in the event on the ring buffer. If they do not, it issues a WARN_ON() as it is a likely bug. But this isn't the case for the strings that can be dereferenced with "%s", as some trace events (notably RCU and some IPI events) save a pointer to a static string in the ring buffer. As the string it points to lives as long as the kernel is running, it is not a bug to reference it, as it is guaranteed to be there when the event is read. But it is also possible (and a common bug) to point to some allocated string that could be freed before the trace event is read and the dereference is to bad memory. This case requires a run time check. The previous way to handle this was with trace_check_vprintf() that would process the printf format piece by piece and send what it didn't care about to vsnprintf() to handle arguments that were not strings. This kept it from having to reimplement vsnprintf(). But it relied on va_list implementation and for architectures that copied the va_list and did not pass it by reference, it wasn't even possible to do this check and it would be skipped. As 64bit x86 passed va_list by reference, most events were tested and this kept out bugs where strings would have been dereferenced after being freed. Instead of relying on the implementation of va_list, extend the boot up test_event_printk() function to validate all the "%s" strings that can be validated at boot, and for the few events that point to strings outside the ring buffer, flag both the event and the field that is dereferenced as "needs_test". Then before the event is printed, a call to ignore_event() is made, and if the event has the flag set, it iterates all its fields and for every field that is to be tested, it will read the pointer directly from the event in the ring buffer and make sure that it is valid. If the pointer is not valid, it will print a WARN_ON(), print out to the trace that the event has unsafe memory and ignore the print format. With this new update, the trace_check_vprintf() can be safely removed and now all events can be verified regardless of architecture. -----BEGIN PGP SIGNATURE----- iIoEABYIADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCZ2IqiRQccm9zdGVkdEBn b29kbWlzLm9yZwAKCRAp5XQQmuv6qlfgAP9hJFl6zhA5GGRo905G9JWFHkbNNjgp WfQ0oMU2Eo1q+AEAmb5d3wWfWJAa+AxiiDNeZ28En/+ZbmjhSe6fPpR4egU= =LRKi -----END PGP SIGNATURE----- Merge tag 'trace-v6.13-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: "Replace trace_check_vprintf() with test_event_printk() and ignore_event() The function test_event_printk() checks on boot up if the trace event printf() formats dereference any pointers, and if they do, it then looks at the arguments to make sure that the pointers they dereference will exist in the event on the ring buffer. If they do not, it issues a WARN_ON() as it is a likely bug. But this isn't the case for the strings that can be dereferenced with "%s", as some trace events (notably RCU and some IPI events) save a pointer to a static string in the ring buffer. As the string it points to lives as long as the kernel is running, it is not a bug to reference it, as it is guaranteed to be there when the event is read. But it is also possible (and a common bug) to point to some allocated string that could be freed before the trace event is read and the dereference is to bad memory. This case requires a run time check. The previous way to handle this was with trace_check_vprintf() that would process the printf format piece by piece and send what it didn't care about to vsnprintf() to handle arguments that were not strings. This kept it from having to reimplement vsnprintf(). But it relied on va_list implementation and for architectures that copied the va_list and did not pass it by reference, it wasn't even possible to do this check and it would be skipped. As 64bit x86 passed va_list by reference, most events were tested and this kept out bugs where strings would have been dereferenced after being freed. Instead of relying on the implementation of va_list, extend the boot up test_event_printk() function to validate all the "%s" strings that can be validated at boot, and for the few events that point to strings outside the ring buffer, flag both the event and the field that is dereferenced as "needs_test". Then before the event is printed, a call to ignore_event() is made, and if the event has the flag set, it iterates all its fields and for every field that is to be tested, it will read the pointer directly from the event in the ring buffer and make sure that it is valid. If the pointer is not valid, it will print a WARN_ON(), print out to the trace that the event has unsafe memory and ignore the print format. With this new update, the trace_check_vprintf() can be safely removed and now all events can be verified regardless of architecture" * tag 'trace-v6.13-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: tracing: Check "%s" dereference via the field and not the TP_printk format tracing: Add "%s" check in test_event_printk() tracing: Add missing helper functions in event pointer dereference check tracing: Fix test_event_printk() to process entire print argument
This commit is contained in:
commit
c061cf420d
@ -273,7 +273,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;
|
||||
};
|
||||
@ -324,6 +325,7 @@ enum {
|
||||
TRACE_EVENT_FL_EPROBE_BIT,
|
||||
TRACE_EVENT_FL_FPROBE_BIT,
|
||||
TRACE_EVENT_FL_CUSTOM_BIT,
|
||||
TRACE_EVENT_FL_TEST_STR_BIT,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -340,6 +342,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_CAP_ANY = (1 << TRACE_EVENT_FL_CAP_ANY_BIT),
|
||||
@ -352,6 +355,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)
|
||||
|
@ -3611,17 +3611,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))
|
||||
@ -3661,181 +3656,69 @@ static bool trace_safe_str(struct trace_iterator *iter, const char *str,
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
long text_delta = 0;
|
||||
long data_delta = 0;
|
||||
const char *p = fmt;
|
||||
const char *str;
|
||||
bool good;
|
||||
int i, j;
|
||||
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 (WARN_ON_ONCE(!fmt))
|
||||
return;
|
||||
trace_event = ftrace_find_event(iter->ent->type);
|
||||
|
||||
if (static_branch_unlikely(&trace_no_verify))
|
||||
goto print;
|
||||
seq = &iter->seq;
|
||||
|
||||
/*
|
||||
* When the kernel is booted with the tp_printk command line
|
||||
* parameter, trace events go directly through to printk().
|
||||
* It also is checked by this function, but it does not
|
||||
* have an associated trace_array (tr) for it.
|
||||
*/
|
||||
if (iter->tr) {
|
||||
text_delta = iter->tr->text_delta;
|
||||
data_delta = iter->tr->data_delta;
|
||||
if (!trace_event) {
|
||||
trace_seq_printf(seq, "EVENT ID %d NOT FOUND?\n", iter->ent->type);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Don't bother checking when doing a ftrace_dump() */
|
||||
if (iter->fmt == static_fmt_buf)
|
||||
goto print;
|
||||
event = container_of(trace_event, struct trace_event_call, event);
|
||||
if (!(event->flags & TRACE_EVENT_FL_TEST_STR))
|
||||
return false;
|
||||
|
||||
while (*p) {
|
||||
bool star = false;
|
||||
int len = 0;
|
||||
head = trace_get_fields(event);
|
||||
if (!head) {
|
||||
trace_seq_printf(seq, "FIELDS FOR EVENT '%s' NOT FOUND?\n",
|
||||
trace_event_name(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
j = 0;
|
||||
/* Offsets are from the iter->ent that points to the raw event */
|
||||
ptr = iter->ent;
|
||||
|
||||
/*
|
||||
* We only care about %s and variants
|
||||
* as well as %p[sS] if delta is non-zero
|
||||
*/
|
||||
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;
|
||||
}
|
||||
list_for_each_entry(field, head, link) {
|
||||
const char *str;
|
||||
bool good;
|
||||
|
||||
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;
|
||||
|
||||
if (text_delta && p[i+1] == 'p' &&
|
||||
((p[i+2] == 's' || p[i+2] == 'S')))
|
||||
break;
|
||||
|
||||
star = false;
|
||||
}
|
||||
j = 0;
|
||||
}
|
||||
/* If no %s found then just print normally */
|
||||
if (!p[i])
|
||||
break;
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Add delta to %pS pointers */
|
||||
if (p[i+1] == 'p') {
|
||||
unsigned long addr;
|
||||
char fmt[4];
|
||||
|
||||
fmt[0] = '%';
|
||||
fmt[1] = 'p';
|
||||
fmt[2] = p[i+2]; /* Either %ps or %pS */
|
||||
fmt[3] = '\0';
|
||||
|
||||
addr = va_arg(ap, unsigned long);
|
||||
addr += text_delta;
|
||||
trace_seq_printf(&iter->seq, fmt, (void *)addr);
|
||||
|
||||
p += i + 3;
|
||||
if (!field->needs_test)
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
str = *(const char **)(ptr + field->offset);
|
||||
|
||||
if (star)
|
||||
len = va_arg(ap, int);
|
||||
|
||||
/* The ap now points to the string data of the %s */
|
||||
str = va_arg(ap, const char *);
|
||||
|
||||
good = trace_safe_str(iter, str, star, len);
|
||||
|
||||
/* Could be from the last boot */
|
||||
if (data_delta && !good) {
|
||||
str += data_delta;
|
||||
good = trace_safe_str(iter, str, star, len);
|
||||
}
|
||||
good = trace_safe_str(iter, str);
|
||||
|
||||
/*
|
||||
* If you hit this warning, it is likely that the
|
||||
@ -3846,44 +3729,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(!good, "fmt: '%s' current_buffer: '%s'",
|
||||
fmt, seq_buf_str(&iter->seq.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 (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 (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 (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)
|
||||
@ -10777,8 +10630,6 @@ __init static int tracer_alloc_buffers(void)
|
||||
|
||||
register_snapshot_cmd();
|
||||
|
||||
test_can_verify();
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_pipe_cpumask:
|
||||
|
@ -667,9 +667,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);
|
||||
|
||||
@ -1413,7 +1412,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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -244,19 +247,16 @@ int trace_event_get_offsets(struct trace_event_call *call)
|
||||
return tail->offset + tail->size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the referenced field is an array and return true,
|
||||
* as arrays are OK to dereference.
|
||||
*/
|
||||
static bool test_field(const char *fmt, struct trace_event_call *call)
|
||||
|
||||
static struct trace_event_fields *find_event_field(const char *fmt,
|
||||
struct trace_event_call *call)
|
||||
{
|
||||
struct trace_event_fields *field = call->class->fields_array;
|
||||
const char *array_descriptor;
|
||||
const char *p = fmt;
|
||||
int len;
|
||||
|
||||
if (!(len = str_has_prefix(fmt, "REC->")))
|
||||
return false;
|
||||
return NULL;
|
||||
fmt += len;
|
||||
for (p = fmt; *p; p++) {
|
||||
if (!isalnum(*p) && *p != '_')
|
||||
@ -265,16 +265,129 @@ static bool test_field(const char *fmt, struct trace_event_call *call)
|
||||
len = p - fmt;
|
||||
|
||||
for (; field->type; field++) {
|
||||
if (strncmp(field->name, fmt, len) ||
|
||||
field->name[len])
|
||||
if (strncmp(field->name, fmt, len) || field->name[len])
|
||||
continue;
|
||||
array_descriptor = strchr(field->type, '[');
|
||||
/* This is an array and is OK to dereference. */
|
||||
return array_descriptor != NULL;
|
||||
|
||||
return field;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the referenced field is an array and return true,
|
||||
* as arrays are OK to dereference.
|
||||
*/
|
||||
static bool test_field(const char *fmt, struct trace_event_call *call)
|
||||
{
|
||||
struct trace_event_fields *field;
|
||||
|
||||
field = find_event_field(fmt, call);
|
||||
if (!field)
|
||||
return false;
|
||||
|
||||
/* This is an array and is OK to dereference. */
|
||||
return strchr(field->type, '[') != NULL;
|
||||
}
|
||||
|
||||
/* Look for a string within an argument */
|
||||
static bool find_print_string(const char *arg, const char *str, const char *end)
|
||||
{
|
||||
const char *r;
|
||||
|
||||
r = strstr(arg, str);
|
||||
return r && r < end;
|
||||
}
|
||||
|
||||
/* Return true if the argument pointer is safe */
|
||||
static bool process_pointer(const char *fmt, int len, struct trace_event_call *call)
|
||||
{
|
||||
const char *r, *e, *a;
|
||||
|
||||
e = fmt + len;
|
||||
|
||||
/* Find the REC-> in the argument */
|
||||
r = strstr(fmt, "REC->");
|
||||
if (r && r < e) {
|
||||
/*
|
||||
* Addresses of events on the buffer, or an array on the buffer is
|
||||
* OK to dereference. There's ways to fool this, but
|
||||
* this is to catch common mistakes, not malicious code.
|
||||
*/
|
||||
a = strchr(fmt, '&');
|
||||
if ((a && (a < r)) || test_field(r, call))
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_dynamic_array(", e)) {
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_rel_dynamic_array(", e)) {
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_dynamic_array_len(", e)) {
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_rel_dynamic_array_len(", e)) {
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_sockaddr(", e)) {
|
||||
return true;
|
||||
} else if (find_print_string(fmt, "__get_rel_sockaddr(", e)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/*
|
||||
* There are several helper functions that return strings.
|
||||
* If the argument contains a function, then assume its field is valid.
|
||||
* It is considered that the argument has a function if it has:
|
||||
* alphanumeric or '_' before a parenthesis.
|
||||
*/
|
||||
s = fmt;
|
||||
do {
|
||||
r = strstr(s, "(");
|
||||
if (!r || r >= e)
|
||||
break;
|
||||
for (int i = 1; r - i >= s; i++) {
|
||||
char ch = *(r - i);
|
||||
if (isspace(ch))
|
||||
continue;
|
||||
if (isalnum(ch) || ch == '_')
|
||||
return true;
|
||||
/* Anything else, this isn't a function */
|
||||
break;
|
||||
}
|
||||
/* A function could be wrapped in parethesis, try the next one */
|
||||
s = r + 1;
|
||||
} while (s < e);
|
||||
|
||||
/*
|
||||
* If there's any strings in the argument consider this arg OK as it
|
||||
* could be: REC->field ? "foo" : "bar" and we don't want to get into
|
||||
* verifying that logic here.
|
||||
*/
|
||||
if (find_print_string(fmt, "\"", e))
|
||||
return true;
|
||||
|
||||
/* Dereferenced strings are also valid like any other pointer */
|
||||
if (process_pointer(fmt, len, call))
|
||||
return true;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine the print fmt of the event looking for unsafe dereference
|
||||
* pointers using %p* that could be recorded in the trace event and
|
||||
@ -284,13 +397,14 @@ static bool test_field(const char *fmt, struct trace_event_call *call)
|
||||
static void test_event_printk(struct trace_event_call *call)
|
||||
{
|
||||
u64 dereference_flags = 0;
|
||||
u64 string_flags = 0;
|
||||
bool first = true;
|
||||
const char *fmt, *c, *r, *a;
|
||||
const char *fmt;
|
||||
int parens = 0;
|
||||
char in_quote = 0;
|
||||
int start_arg = 0;
|
||||
int arg = 0;
|
||||
int i;
|
||||
int i, e;
|
||||
|
||||
fmt = call->print_fmt;
|
||||
|
||||
@ -374,8 +488,16 @@ static void test_event_printk(struct trace_event_call *call)
|
||||
star = true;
|
||||
continue;
|
||||
}
|
||||
if ((fmt[i + j] == 's') && star)
|
||||
arg++;
|
||||
if ((fmt[i + j] == 's')) {
|
||||
if (star)
|
||||
arg++;
|
||||
if (WARN_ONCE(arg == 63,
|
||||
"Too many args for event: %s",
|
||||
trace_event_name(call)))
|
||||
return;
|
||||
dereference_flags |= 1ULL << arg;
|
||||
string_flags |= 1ULL << arg;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -403,42 +525,47 @@ static void test_event_printk(struct trace_event_call *call)
|
||||
case ',':
|
||||
if (in_quote || parens)
|
||||
continue;
|
||||
e = i;
|
||||
i++;
|
||||
while (isspace(fmt[i]))
|
||||
i++;
|
||||
start_arg = i;
|
||||
if (!(dereference_flags & (1ULL << arg)))
|
||||
goto next_arg;
|
||||
|
||||
/* Find the REC-> in the argument */
|
||||
c = strchr(fmt + i, ',');
|
||||
r = strstr(fmt + i, "REC->");
|
||||
if (r && (!c || r < c)) {
|
||||
/*
|
||||
* Addresses of events on the buffer,
|
||||
* or an array on the buffer is
|
||||
* OK to dereference.
|
||||
* There's ways to fool this, but
|
||||
* this is to catch common mistakes,
|
||||
* not malicious code.
|
||||
*/
|
||||
a = strchr(fmt + i, '&');
|
||||
if ((a && (a < r)) || test_field(r, call))
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
} else if ((r = strstr(fmt + i, "__get_dynamic_array(")) &&
|
||||
(!c || r < c)) {
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
} else if ((r = strstr(fmt + i, "__get_sockaddr(")) &&
|
||||
(!c || r < c)) {
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
/*
|
||||
* If start_arg is zero, then this is the start of the
|
||||
* first argument. The processing of the argument happens
|
||||
* when the end of the argument is found, as it needs to
|
||||
* handle paranthesis and such.
|
||||
*/
|
||||
if (!start_arg) {
|
||||
start_arg = i;
|
||||
/* Balance out the i++ in the for loop */
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_arg:
|
||||
i--;
|
||||
if (dereference_flags & (1ULL << arg)) {
|
||||
if (string_flags & (1ULL << arg)) {
|
||||
if (process_string(fmt + start_arg, e - start_arg, call))
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
} else if (process_pointer(fmt + start_arg, e - start_arg, call))
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
}
|
||||
|
||||
start_arg = i;
|
||||
arg++;
|
||||
/* Balance out the i++ in the for loop */
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (dereference_flags & (1ULL << arg)) {
|
||||
if (string_flags & (1ULL << arg)) {
|
||||
if (process_string(fmt + start_arg, i - start_arg, call))
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
} else if (process_pointer(fmt + start_arg, i - start_arg, call))
|
||||
dereference_flags &= ~(1ULL << arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* If you triggered the below warning, the trace event reported
|
||||
* uses an unsafe dereference pointer %p*. As the data stored
|
||||
@ -2471,7 +2598,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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user