forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
6.3 KiB
327 lines
6.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* trace_events_inject - trace event injection |
|
* |
|
* Copyright (C) 2019 Cong Wang <[email protected]> |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/ctype.h> |
|
#include <linux/mutex.h> |
|
#include <linux/slab.h> |
|
#include <linux/rculist.h> |
|
|
|
#include "trace.h" |
|
|
|
static int |
|
trace_inject_entry(struct trace_event_file *file, void *rec, int len) |
|
{ |
|
struct trace_event_buffer fbuffer; |
|
int written = 0; |
|
void *entry; |
|
|
|
rcu_read_lock_sched(); |
|
entry = trace_event_buffer_reserve(&fbuffer, file, len); |
|
if (entry) { |
|
memcpy(entry, rec, len); |
|
written = len; |
|
trace_event_buffer_commit(&fbuffer); |
|
} |
|
rcu_read_unlock_sched(); |
|
|
|
return written; |
|
} |
|
|
|
static int |
|
parse_field(char *str, struct trace_event_call *call, |
|
struct ftrace_event_field **pf, u64 *pv) |
|
{ |
|
struct ftrace_event_field *field; |
|
char *field_name; |
|
int s, i = 0; |
|
int len; |
|
u64 val; |
|
|
|
if (!str[i]) |
|
return 0; |
|
/* First find the field to associate to */ |
|
while (isspace(str[i])) |
|
i++; |
|
s = i; |
|
while (isalnum(str[i]) || str[i] == '_') |
|
i++; |
|
len = i - s; |
|
if (!len) |
|
return -EINVAL; |
|
|
|
field_name = kmemdup_nul(str + s, len, GFP_KERNEL); |
|
if (!field_name) |
|
return -ENOMEM; |
|
field = trace_find_event_field(call, field_name); |
|
kfree(field_name); |
|
if (!field) |
|
return -ENOENT; |
|
|
|
*pf = field; |
|
while (isspace(str[i])) |
|
i++; |
|
if (str[i] != '=') |
|
return -EINVAL; |
|
i++; |
|
while (isspace(str[i])) |
|
i++; |
|
s = i; |
|
if (isdigit(str[i]) || str[i] == '-') { |
|
char *num, c; |
|
int ret; |
|
|
|
/* Make sure the field is not a string */ |
|
if (is_string_field(field)) |
|
return -EINVAL; |
|
|
|
if (str[i] == '-') |
|
i++; |
|
|
|
/* We allow 0xDEADBEEF */ |
|
while (isalnum(str[i])) |
|
i++; |
|
num = str + s; |
|
c = str[i]; |
|
if (c != '\0' && !isspace(c)) |
|
return -EINVAL; |
|
str[i] = '\0'; |
|
/* Make sure it is a value */ |
|
if (field->is_signed) |
|
ret = kstrtoll(num, 0, &val); |
|
else |
|
ret = kstrtoull(num, 0, &val); |
|
str[i] = c; |
|
if (ret) |
|
return ret; |
|
|
|
*pv = val; |
|
return i; |
|
} else if (str[i] == '\'' || str[i] == '"') { |
|
char q = str[i]; |
|
|
|
/* Make sure the field is OK for strings */ |
|
if (!is_string_field(field)) |
|
return -EINVAL; |
|
|
|
for (i++; str[i]; i++) { |
|
if (str[i] == '\\' && str[i + 1]) { |
|
i++; |
|
continue; |
|
} |
|
if (str[i] == q) |
|
break; |
|
} |
|
if (!str[i]) |
|
return -EINVAL; |
|
|
|
/* Skip quotes */ |
|
s++; |
|
len = i - s; |
|
if (len >= MAX_FILTER_STR_VAL) |
|
return -EINVAL; |
|
|
|
*pv = (unsigned long)(str + s); |
|
str[i] = 0; |
|
/* go past the last quote */ |
|
i++; |
|
return i; |
|
} |
|
|
|
return -EINVAL; |
|
} |
|
|
|
static int trace_get_entry_size(struct trace_event_call *call) |
|
{ |
|
struct ftrace_event_field *field; |
|
struct list_head *head; |
|
int size = 0; |
|
|
|
head = trace_get_fields(call); |
|
list_for_each_entry(field, head, link) { |
|
if (field->size + field->offset > size) |
|
size = field->size + field->offset; |
|
} |
|
|
|
return size; |
|
} |
|
|
|
static void *trace_alloc_entry(struct trace_event_call *call, int *size) |
|
{ |
|
int entry_size = trace_get_entry_size(call); |
|
struct ftrace_event_field *field; |
|
struct list_head *head; |
|
void *entry = NULL; |
|
|
|
/* We need an extra '\0' at the end. */ |
|
entry = kzalloc(entry_size + 1, GFP_KERNEL); |
|
if (!entry) |
|
return NULL; |
|
|
|
head = trace_get_fields(call); |
|
list_for_each_entry(field, head, link) { |
|
if (!is_string_field(field)) |
|
continue; |
|
if (field->filter_type == FILTER_STATIC_STRING) |
|
continue; |
|
if (field->filter_type == FILTER_DYN_STRING) { |
|
u32 *str_item; |
|
int str_loc = entry_size & 0xffff; |
|
|
|
str_item = (u32 *)(entry + field->offset); |
|
*str_item = str_loc; /* string length is 0. */ |
|
} else { |
|
char **paddr; |
|
|
|
paddr = (char **)(entry + field->offset); |
|
*paddr = ""; |
|
} |
|
} |
|
|
|
*size = entry_size + 1; |
|
return entry; |
|
} |
|
|
|
#define INJECT_STRING "STATIC STRING CAN NOT BE INJECTED" |
|
|
|
/* Caller is responsible to free the *pentry. */ |
|
static int parse_entry(char *str, struct trace_event_call *call, void **pentry) |
|
{ |
|
struct ftrace_event_field *field; |
|
void *entry = NULL; |
|
int entry_size; |
|
u64 val = 0; |
|
int len; |
|
|
|
entry = trace_alloc_entry(call, &entry_size); |
|
*pentry = entry; |
|
if (!entry) |
|
return -ENOMEM; |
|
|
|
tracing_generic_entry_update(entry, call->event.type, |
|
tracing_gen_ctx()); |
|
|
|
while ((len = parse_field(str, call, &field, &val)) > 0) { |
|
if (is_function_field(field)) |
|
return -EINVAL; |
|
|
|
if (is_string_field(field)) { |
|
char *addr = (char *)(unsigned long) val; |
|
|
|
if (field->filter_type == FILTER_STATIC_STRING) { |
|
strlcpy(entry + field->offset, addr, field->size); |
|
} else if (field->filter_type == FILTER_DYN_STRING) { |
|
int str_len = strlen(addr) + 1; |
|
int str_loc = entry_size & 0xffff; |
|
u32 *str_item; |
|
|
|
entry_size += str_len; |
|
*pentry = krealloc(entry, entry_size, GFP_KERNEL); |
|
if (!*pentry) { |
|
kfree(entry); |
|
return -ENOMEM; |
|
} |
|
entry = *pentry; |
|
|
|
strlcpy(entry + (entry_size - str_len), addr, str_len); |
|
str_item = (u32 *)(entry + field->offset); |
|
*str_item = (str_len << 16) | str_loc; |
|
} else { |
|
char **paddr; |
|
|
|
paddr = (char **)(entry + field->offset); |
|
*paddr = INJECT_STRING; |
|
} |
|
} else { |
|
switch (field->size) { |
|
case 1: { |
|
u8 tmp = (u8) val; |
|
|
|
memcpy(entry + field->offset, &tmp, 1); |
|
break; |
|
} |
|
case 2: { |
|
u16 tmp = (u16) val; |
|
|
|
memcpy(entry + field->offset, &tmp, 2); |
|
break; |
|
} |
|
case 4: { |
|
u32 tmp = (u32) val; |
|
|
|
memcpy(entry + field->offset, &tmp, 4); |
|
break; |
|
} |
|
case 8: |
|
memcpy(entry + field->offset, &val, 8); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
str += len; |
|
} |
|
|
|
if (len < 0) |
|
return len; |
|
|
|
return entry_size; |
|
} |
|
|
|
static ssize_t |
|
event_inject_write(struct file *filp, const char __user *ubuf, size_t cnt, |
|
loff_t *ppos) |
|
{ |
|
struct trace_event_call *call; |
|
struct trace_event_file *file; |
|
int err = -ENODEV, size; |
|
void *entry = NULL; |
|
char *buf; |
|
|
|
if (cnt >= PAGE_SIZE) |
|
return -EINVAL; |
|
|
|
buf = memdup_user_nul(ubuf, cnt); |
|
if (IS_ERR(buf)) |
|
return PTR_ERR(buf); |
|
strim(buf); |
|
|
|
mutex_lock(&event_mutex); |
|
file = event_file_data(filp); |
|
if (file) { |
|
call = file->event_call; |
|
size = parse_entry(buf, call, &entry); |
|
if (size < 0) |
|
err = size; |
|
else |
|
err = trace_inject_entry(file, entry, size); |
|
} |
|
mutex_unlock(&event_mutex); |
|
|
|
kfree(entry); |
|
kfree(buf); |
|
|
|
if (err < 0) |
|
return err; |
|
|
|
*ppos += err; |
|
return cnt; |
|
} |
|
|
|
static ssize_t |
|
event_inject_read(struct file *file, char __user *buf, size_t size, |
|
loff_t *ppos) |
|
{ |
|
return -EPERM; |
|
} |
|
|
|
const struct file_operations event_inject_fops = { |
|
.open = tracing_open_generic, |
|
.read = event_inject_read, |
|
.write = event_inject_write, |
|
};
|
|
|