mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-28 16:53:49 +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 char *name;
|
||||||
const int size;
|
const int size;
|
||||||
const int align;
|
const int align;
|
||||||
const int is_signed;
|
const unsigned int is_signed:1;
|
||||||
|
unsigned int needs_test:1;
|
||||||
const int filter_type;
|
const int filter_type;
|
||||||
const int len;
|
const int len;
|
||||||
};
|
};
|
||||||
@ -324,6 +325,7 @@ enum {
|
|||||||
TRACE_EVENT_FL_EPROBE_BIT,
|
TRACE_EVENT_FL_EPROBE_BIT,
|
||||||
TRACE_EVENT_FL_FPROBE_BIT,
|
TRACE_EVENT_FL_FPROBE_BIT,
|
||||||
TRACE_EVENT_FL_CUSTOM_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)
|
* CUSTOM - Event is a custom event (to be attached to an exsiting tracepoint)
|
||||||
* This is set when the custom event has not been attached
|
* This is set when the custom event has not been attached
|
||||||
* to a tracepoint yet, then it is cleared when it is.
|
* 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 {
|
enum {
|
||||||
TRACE_EVENT_FL_CAP_ANY = (1 << TRACE_EVENT_FL_CAP_ANY_BIT),
|
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_EPROBE = (1 << TRACE_EVENT_FL_EPROBE_BIT),
|
||||||
TRACE_EVENT_FL_FPROBE = (1 << TRACE_EVENT_FL_FPROBE_BIT),
|
TRACE_EVENT_FL_FPROBE = (1 << TRACE_EVENT_FL_FPROBE_BIT),
|
||||||
TRACE_EVENT_FL_CUSTOM = (1 << TRACE_EVENT_FL_CUSTOM_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)
|
#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 */
|
/* Returns true if the string is safe to dereference from an event */
|
||||||
static bool trace_safe_str(struct trace_iterator *iter, const char *str,
|
static bool trace_safe_str(struct trace_iterator *iter, const char *str)
|
||||||
bool star, int len)
|
|
||||||
{
|
{
|
||||||
unsigned long addr = (unsigned long)str;
|
unsigned long addr = (unsigned long)str;
|
||||||
struct trace_event *trace_event;
|
struct trace_event *trace_event;
|
||||||
struct trace_event_call *event;
|
struct trace_event_call *event;
|
||||||
|
|
||||||
/* Ignore strings with no length */
|
|
||||||
if (star && !len)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
/* OK if part of the event data */
|
/* OK if part of the event data */
|
||||||
if ((addr >= (unsigned long)iter->ent) &&
|
if ((addr >= (unsigned long)iter->ent) &&
|
||||||
(addr < (unsigned long)iter->ent + iter->ent_size))
|
(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;
|
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
|
* @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
|
* At boot up, test_event_printk() will flag any event that dereferences
|
||||||
* @fmt and @ap. If the format has a %s, then the source of the string
|
* a string with "%s" that does exist in the ring buffer. It may still
|
||||||
* is examined to make sure it is safe to print, otherwise it will
|
* be valid, as the string may point to a static string in the kernel
|
||||||
* warn and print "[UNSAFE MEMORY]" in place of the dereferenced string
|
* rodata that never gets freed. But if the string pointer is pointing
|
||||||
* pointer.
|
* 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,
|
bool ignore_event(struct trace_iterator *iter)
|
||||||
va_list ap)
|
|
||||||
{
|
{
|
||||||
long text_delta = 0;
|
struct ftrace_event_field *field;
|
||||||
long data_delta = 0;
|
struct trace_event *trace_event;
|
||||||
const char *p = fmt;
|
struct trace_event_call *event;
|
||||||
const char *str;
|
struct list_head *head;
|
||||||
bool good;
|
struct trace_seq *seq;
|
||||||
int i, j;
|
const void *ptr;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(!fmt))
|
trace_event = ftrace_find_event(iter->ent->type);
|
||||||
return;
|
|
||||||
|
|
||||||
if (static_branch_unlikely(&trace_no_verify))
|
seq = &iter->seq;
|
||||||
goto print;
|
|
||||||
|
|
||||||
/*
|
if (!trace_event) {
|
||||||
* When the kernel is booted with the tp_printk command line
|
trace_seq_printf(seq, "EVENT ID %d NOT FOUND?\n", iter->ent->type);
|
||||||
* parameter, trace events go directly through to printk().
|
return true;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't bother checking when doing a ftrace_dump() */
|
event = container_of(trace_event, struct trace_event_call, event);
|
||||||
if (iter->fmt == static_fmt_buf)
|
if (!(event->flags & TRACE_EVENT_FL_TEST_STR))
|
||||||
goto print;
|
return false;
|
||||||
|
|
||||||
while (*p) {
|
head = trace_get_fields(event);
|
||||||
bool star = false;
|
if (!head) {
|
||||||
int len = 0;
|
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;
|
||||||
|
|
||||||
/*
|
list_for_each_entry(field, head, link) {
|
||||||
* We only care about %s and variants
|
const char *str;
|
||||||
* as well as %p[sS] if delta is non-zero
|
bool good;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p[i] == '\\' && p[i+1]) {
|
if (!field->needs_test)
|
||||||
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;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
str = *(const char **)(ptr + field->offset);
|
||||||
* 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 (star)
|
good = trace_safe_str(iter, str);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If you hit this warning, it is likely that the
|
* 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
|
* instead. See samples/trace_events/trace-events-sample.h
|
||||||
* for reference.
|
* for reference.
|
||||||
*/
|
*/
|
||||||
if (WARN_ONCE(!good, "fmt: '%s' current_buffer: '%s'",
|
if (WARN_ONCE(!good, "event '%s' has unsafe pointer field '%s'",
|
||||||
fmt, seq_buf_str(&iter->seq.seq))) {
|
trace_event_name(event), field->name)) {
|
||||||
int ret;
|
trace_seq_printf(seq, "EVENT %s: HAS UNSAFE POINTER FIELD '%s'\n",
|
||||||
|
trace_event_name(event), field->name);
|
||||||
/* Try to safely read the string */
|
return true;
|
||||||
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 (star)
|
|
||||||
trace_seq_printf(&iter->seq, iter->fmt, len, str);
|
|
||||||
else
|
|
||||||
trace_seq_printf(&iter->seq, iter->fmt, str);
|
|
||||||
|
|
||||||
p += i + j + 1;
|
|
||||||
}
|
}
|
||||||
print:
|
return false;
|
||||||
if (*p)
|
|
||||||
trace_seq_vprintf(&iter->seq, p, ap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *trace_event_format(struct trace_iterator *iter, const char *fmt)
|
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();
|
register_snapshot_cmd();
|
||||||
|
|
||||||
test_can_verify();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_free_pipe_cpumask:
|
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);
|
bool trace_is_tracepoint_string(const char *str);
|
||||||
const char *trace_event_format(struct trace_iterator *iter, const char *fmt);
|
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);
|
char *trace_iter_expand_format(struct trace_iterator *iter);
|
||||||
|
bool ignore_event(struct trace_iterator *iter);
|
||||||
|
|
||||||
int trace_empty(struct trace_iterator *iter);
|
int trace_empty(struct trace_iterator *iter);
|
||||||
|
|
||||||
@ -1413,7 +1412,8 @@ struct ftrace_event_field {
|
|||||||
int filter_type;
|
int filter_type;
|
||||||
int offset;
|
int offset;
|
||||||
int size;
|
int size;
|
||||||
int is_signed;
|
unsigned int is_signed:1;
|
||||||
|
unsigned int needs_test:1;
|
||||||
int len;
|
int len;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ static int system_refcount_dec(struct event_subsystem *system)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct ftrace_event_field *
|
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;
|
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,
|
static int __trace_define_field(struct list_head *head, const char *type,
|
||||||
const char *name, int offset, int size,
|
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;
|
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->offset = offset;
|
||||||
field->size = size;
|
field->size = size;
|
||||||
field->is_signed = is_signed;
|
field->is_signed = is_signed;
|
||||||
|
field->needs_test = need_test;
|
||||||
field->len = len;
|
field->len = len;
|
||||||
|
|
||||||
list_add(&field->link, head);
|
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);
|
head = trace_get_fields(call);
|
||||||
return __trace_define_field(head, type, name, offset, size,
|
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);
|
EXPORT_SYMBOL_GPL(trace_define_field);
|
||||||
|
|
||||||
static int trace_define_field_ext(struct trace_event_call *call, const char *type,
|
static int trace_define_field_ext(struct trace_event_call *call, const char *type,
|
||||||
const char *name, int offset, int size, int is_signed,
|
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;
|
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);
|
head = trace_get_fields(call);
|
||||||
return __trace_define_field(head, type, name, offset, size,
|
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) \
|
#define __generic_field(type, item, filter_type) \
|
||||||
ret = __trace_define_field(&ftrace_generic_fields, #type, \
|
ret = __trace_define_field(&ftrace_generic_fields, #type, \
|
||||||
#item, 0, 0, is_signed_type(type), \
|
#item, 0, 0, is_signed_type(type), \
|
||||||
filter_type, 0); \
|
filter_type, 0, 0); \
|
||||||
if (ret) \
|
if (ret) \
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -181,7 +183,8 @@ static int trace_define_field_ext(struct trace_event_call *call, const char *typ
|
|||||||
"common_" #item, \
|
"common_" #item, \
|
||||||
offsetof(typeof(ent), item), \
|
offsetof(typeof(ent), item), \
|
||||||
sizeof(ent.item), \
|
sizeof(ent.item), \
|
||||||
is_signed_type(type), FILTER_OTHER, 0); \
|
is_signed_type(type), FILTER_OTHER, \
|
||||||
|
0, 0); \
|
||||||
if (ret) \
|
if (ret) \
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -244,19 +247,16 @@ int trace_event_get_offsets(struct trace_event_call *call)
|
|||||||
return tail->offset + tail->size;
|
return tail->offset + tail->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if the referenced field is an array and return true,
|
static struct trace_event_fields *find_event_field(const char *fmt,
|
||||||
* as arrays are OK to dereference.
|
struct trace_event_call *call)
|
||||||
*/
|
|
||||||
static bool test_field(const char *fmt, struct trace_event_call *call)
|
|
||||||
{
|
{
|
||||||
struct trace_event_fields *field = call->class->fields_array;
|
struct trace_event_fields *field = call->class->fields_array;
|
||||||
const char *array_descriptor;
|
|
||||||
const char *p = fmt;
|
const char *p = fmt;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
if (!(len = str_has_prefix(fmt, "REC->")))
|
if (!(len = str_has_prefix(fmt, "REC->")))
|
||||||
return false;
|
return NULL;
|
||||||
fmt += len;
|
fmt += len;
|
||||||
for (p = fmt; *p; p++) {
|
for (p = fmt; *p; p++) {
|
||||||
if (!isalnum(*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;
|
len = p - fmt;
|
||||||
|
|
||||||
for (; field->type; field++) {
|
for (; field->type; field++) {
|
||||||
if (strncmp(field->name, fmt, len) ||
|
if (strncmp(field->name, fmt, len) || field->name[len])
|
||||||
field->name[len])
|
|
||||||
continue;
|
continue;
|
||||||
array_descriptor = strchr(field->type, '[');
|
|
||||||
/* This is an array and is OK to dereference. */
|
return field;
|
||||||
return array_descriptor != NULL;
|
}
|
||||||
|
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 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
|
* Examine the print fmt of the event looking for unsafe dereference
|
||||||
* pointers using %p* that could be recorded in the trace event and
|
* 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)
|
static void test_event_printk(struct trace_event_call *call)
|
||||||
{
|
{
|
||||||
u64 dereference_flags = 0;
|
u64 dereference_flags = 0;
|
||||||
|
u64 string_flags = 0;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
const char *fmt, *c, *r, *a;
|
const char *fmt;
|
||||||
int parens = 0;
|
int parens = 0;
|
||||||
char in_quote = 0;
|
char in_quote = 0;
|
||||||
int start_arg = 0;
|
int start_arg = 0;
|
||||||
int arg = 0;
|
int arg = 0;
|
||||||
int i;
|
int i, e;
|
||||||
|
|
||||||
fmt = call->print_fmt;
|
fmt = call->print_fmt;
|
||||||
|
|
||||||
@ -374,8 +488,16 @@ static void test_event_printk(struct trace_event_call *call)
|
|||||||
star = true;
|
star = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((fmt[i + j] == 's') && star)
|
if ((fmt[i + j] == 's')) {
|
||||||
arg++;
|
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;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -403,42 +525,47 @@ static void test_event_printk(struct trace_event_call *call)
|
|||||||
case ',':
|
case ',':
|
||||||
if (in_quote || parens)
|
if (in_quote || parens)
|
||||||
continue;
|
continue;
|
||||||
|
e = i;
|
||||||
i++;
|
i++;
|
||||||
while (isspace(fmt[i]))
|
while (isspace(fmt[i]))
|
||||||
i++;
|
i++;
|
||||||
start_arg = i;
|
|
||||||
if (!(dereference_flags & (1ULL << arg)))
|
|
||||||
goto next_arg;
|
|
||||||
|
|
||||||
/* Find the REC-> in the argument */
|
/*
|
||||||
c = strchr(fmt + i, ',');
|
* If start_arg is zero, then this is the start of the
|
||||||
r = strstr(fmt + i, "REC->");
|
* first argument. The processing of the argument happens
|
||||||
if (r && (!c || r < c)) {
|
* when the end of the argument is found, as it needs to
|
||||||
/*
|
* handle paranthesis and such.
|
||||||
* Addresses of events on the buffer,
|
*/
|
||||||
* or an array on the buffer is
|
if (!start_arg) {
|
||||||
* OK to dereference.
|
start_arg = i;
|
||||||
* There's ways to fool this, but
|
/* Balance out the i++ in the for loop */
|
||||||
* this is to catch common mistakes,
|
i--;
|
||||||
* not malicious code.
|
continue;
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next_arg:
|
if (dereference_flags & (1ULL << arg)) {
|
||||||
i--;
|
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++;
|
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
|
* If you triggered the below warning, the trace event reported
|
||||||
* uses an unsafe dereference pointer %p*. As the data stored
|
* 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,
|
ret = trace_define_field_ext(call, field->type, field->name,
|
||||||
offset, field->size,
|
offset, field->size,
|
||||||
field->is_signed, field->filter_type,
|
field->is_signed, field->filter_type,
|
||||||
field->len);
|
field->len, field->needs_test);
|
||||||
if (WARN_ON_ONCE(ret)) {
|
if (WARN_ON_ONCE(ret)) {
|
||||||
pr_err("error code is %d\n", ret);
|
pr_err("error code is %d\n", ret);
|
||||||
break;
|
break;
|
||||||
|
@ -317,10 +317,14 @@ EXPORT_SYMBOL(trace_raw_output_prep);
|
|||||||
|
|
||||||
void trace_event_printf(struct trace_iterator *iter, const char *fmt, ...)
|
void trace_event_printf(struct trace_iterator *iter, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
|
struct trace_seq *s = &iter->seq;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
if (ignore_event(iter))
|
||||||
|
return;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
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);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(trace_event_printf);
|
EXPORT_SYMBOL(trace_event_printf);
|
||||||
|
Loading…
Reference in New Issue
Block a user