// 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, 
 | 
}; 
 |