mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-29 17:23:36 +00:00
tracing: Add check of trace event print fmts for dereferencing pointers
Trace events record data into the ring buffer at the time of the event. The trace event has a printf logic to display the recorded data at a much later time when the user reads the trace file. This makes using dereferencing pointers unsafe if the dereferenced pointer points to the original source. The safe way to handle this is to create an array within the trace event and copy the source into the array. Then the dereference pointer may point to that array. As this is a easy mistake to make, a check is added to examine all trace event print fmts to make sure that they are safe to use. This only checks the various %p* dereferenced pointers like %pB, %pR, etc. It does not handle dereferencing of strings, as there are some use cases that are OK to dereference the source. That will be dealt with differently. Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
This commit is contained in:
parent
e0196ae732
commit
5013f454a3
@ -217,6 +217,214 @@ 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)
|
||||
{
|
||||
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;
|
||||
fmt += len;
|
||||
for (p = fmt; *p; p++) {
|
||||
if (!isalnum(*p) && *p != '_')
|
||||
break;
|
||||
}
|
||||
len = p - fmt;
|
||||
|
||||
for (; field->type; field++) {
|
||||
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 false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine the print fmt of the event looking for unsafe dereference
|
||||
* pointers using %p* that could be recorded in the trace event and
|
||||
* much later referenced after the pointer was freed. Dereferencing
|
||||
* pointers are OK, if it is dereferenced into the event itself.
|
||||
*/
|
||||
static void test_event_printk(struct trace_event_call *call)
|
||||
{
|
||||
u64 dereference_flags = 0;
|
||||
bool first = true;
|
||||
const char *fmt, *c, *r, *a;
|
||||
int parens = 0;
|
||||
char in_quote = 0;
|
||||
int start_arg = 0;
|
||||
int arg = 0;
|
||||
int i;
|
||||
|
||||
fmt = call->print_fmt;
|
||||
|
||||
if (!fmt)
|
||||
return;
|
||||
|
||||
for (i = 0; fmt[i]; i++) {
|
||||
switch (fmt[i]) {
|
||||
case '\\':
|
||||
i++;
|
||||
if (!fmt[i])
|
||||
return;
|
||||
continue;
|
||||
case '"':
|
||||
case '\'':
|
||||
/*
|
||||
* The print fmt starts with a string that
|
||||
* is processed first to find %p* usage,
|
||||
* then after the first string, the print fmt
|
||||
* contains arguments that are used to check
|
||||
* if the dereferenced %p* usage is safe.
|
||||
*/
|
||||
if (first) {
|
||||
if (fmt[i] == '\'')
|
||||
continue;
|
||||
if (in_quote) {
|
||||
arg = 0;
|
||||
first = false;
|
||||
/*
|
||||
* If there was no %p* uses
|
||||
* the fmt is OK.
|
||||
*/
|
||||
if (!dereference_flags)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (in_quote) {
|
||||
if (in_quote == fmt[i])
|
||||
in_quote = 0;
|
||||
} else {
|
||||
in_quote = fmt[i];
|
||||
}
|
||||
continue;
|
||||
case '%':
|
||||
if (!first || !in_quote)
|
||||
continue;
|
||||
i++;
|
||||
if (!fmt[i])
|
||||
return;
|
||||
switch (fmt[i]) {
|
||||
case '%':
|
||||
continue;
|
||||
case 'p':
|
||||
/* Find dereferencing fields */
|
||||
switch (fmt[i + 1]) {
|
||||
case 'B': case 'R': case 'r':
|
||||
case 'b': case 'M': case 'm':
|
||||
case 'I': case 'i': case 'E':
|
||||
case 'U': case 'V': case 'N':
|
||||
case 'a': case 'd': case 'D':
|
||||
case 'g': case 't': case 'C':
|
||||
case 'O': case 'f':
|
||||
if (WARN_ONCE(arg == 63,
|
||||
"Too many args for event: %s",
|
||||
trace_event_name(call)))
|
||||
return;
|
||||
dereference_flags |= 1ULL << arg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
bool star = false;
|
||||
int j;
|
||||
|
||||
/* Increment arg if %*s exists. */
|
||||
for (j = 0; fmt[i + j]; j++) {
|
||||
if (isdigit(fmt[i + j]) ||
|
||||
fmt[i + j] == '.')
|
||||
continue;
|
||||
if (fmt[i + j] == '*') {
|
||||
star = true;
|
||||
continue;
|
||||
}
|
||||
if ((fmt[i + j] == 's') && star)
|
||||
arg++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} /* default */
|
||||
|
||||
} /* switch */
|
||||
arg++;
|
||||
continue;
|
||||
case '(':
|
||||
if (in_quote)
|
||||
continue;
|
||||
parens++;
|
||||
continue;
|
||||
case ')':
|
||||
if (in_quote)
|
||||
continue;
|
||||
parens--;
|
||||
if (WARN_ONCE(parens < 0,
|
||||
"Paren mismatch for event: %s\narg='%s'\n%*s",
|
||||
trace_event_name(call),
|
||||
fmt + start_arg,
|
||||
(i - start_arg) + 5, "^"))
|
||||
return;
|
||||
continue;
|
||||
case ',':
|
||||
if (in_quote || parens)
|
||||
continue;
|
||||
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);
|
||||
}
|
||||
next_arg:
|
||||
i--;
|
||||
arg++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If you triggered the below warning, the trace event reported
|
||||
* uses an unsafe dereference pointer %p*. As the data stored
|
||||
* at the trace event time may no longer exist when the trace
|
||||
* event is printed, dereferencing to the original source is
|
||||
* unsafe. The source of the dereference must be copied into the
|
||||
* event itself, and the dereference must access the copy instead.
|
||||
*/
|
||||
if (WARN_ON_ONCE(dereference_flags)) {
|
||||
arg = 1;
|
||||
while (!(dereference_flags & 1)) {
|
||||
dereference_flags >>= 1;
|
||||
arg++;
|
||||
}
|
||||
pr_warn("event %s has unsafe dereference of argument %d\n",
|
||||
trace_event_name(call), arg);
|
||||
pr_warn("print_fmt: %s\n", fmt);
|
||||
}
|
||||
}
|
||||
|
||||
int trace_event_raw_init(struct trace_event_call *call)
|
||||
{
|
||||
int id;
|
||||
@ -225,6 +433,8 @@ int trace_event_raw_init(struct trace_event_call *call)
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
test_event_printk(call);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_event_raw_init);
|
||||
|
Loading…
Reference in New Issue
Block a user