/kernel/trace/trace_events_inject.c
https://github.com/tekkamanninja/linux · C · 327 lines · 304 code · 13 blank · 10 comment · 30 complexity · 5ff632c612a878d6681b366c5ed279f8 MD5 · raw file
- // SPDX-License-Identifier: GPL-2.0
- /*
- * trace_events_inject - trace event injection
- *
- * Copyright (C) 2019 Cong Wang <cwang@twitter.com>
- */
- #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,
- };