335 lines · 6.7 KB · C
RawDownload
// 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 inttrace_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 intparse_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 ||		    field->filter_type == FILTER_RDYN_STRING) {			u32 *str_item;			int str_loc = entry_size & 0xffff; 			if (field->filter_type == FILTER_RDYN_STRING)				str_loc -= field->offset + field->size; 			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) {				strscpy(entry + field->offset, addr, field->size);			} else if (field->filter_type == FILTER_DYN_STRING ||				   field->filter_type == FILTER_RDYN_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; 				strscpy(entry + (entry_size - str_len), addr, str_len);				str_item = (u32 *)(entry + field->offset);				if (field->filter_type == FILTER_RDYN_STRING)					str_loc -= field->offset + field->size;				*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_tevent_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_file(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_tevent_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_file_tr,	.read = event_inject_read,	.write = event_inject_write,	.release = tracing_release_file_tr,};