1// SPDX-License-Identifier: GPL-2.0
2/*
3 * trace_events_inject - trace event injection
4 *
5 * Copyright (C) 2019 Cong Wang <cwang@twitter.com>
6 */
7
8#include <linux/module.h>
9#include <linux/ctype.h>
10#include <linux/mutex.h>
11#include <linux/slab.h>
12#include <linux/rculist.h>
13
14#include "trace.h"
15
16static int
17trace_inject_entry(struct trace_event_file *file, void *rec, int len)
18{
19	struct trace_event_buffer fbuffer;
20	int written = 0;
21	void *entry;
22
23	rcu_read_lock_sched();
24	entry = trace_event_buffer_reserve(&fbuffer, file, len);
25	if (entry) {
26		memcpy(entry, rec, len);
27		written = len;
28		trace_event_buffer_commit(&fbuffer);
29	}
30	rcu_read_unlock_sched();
31
32	return written;
33}
34
35static int
36parse_field(char *str, struct trace_event_call *call,
37	    struct ftrace_event_field **pf, u64 *pv)
38{
39	struct ftrace_event_field *field;
40	char *field_name;
41	int s, i = 0;
42	int len;
43	u64 val;
44
45	if (!str[i])
46		return 0;
47	/* First find the field to associate to */
48	while (isspace(str[i]))
49		i++;
50	s = i;
51	while (isalnum(str[i]) || str[i] == '_')
52		i++;
53	len = i - s;
54	if (!len)
55		return -EINVAL;
56
57	field_name = kmemdup_nul(str + s, len, GFP_KERNEL);
58	if (!field_name)
59		return -ENOMEM;
60	field = trace_find_event_field(call, field_name);
61	kfree(field_name);
62	if (!field)
63		return -ENOENT;
64
65	*pf = field;
66	while (isspace(str[i]))
67		i++;
68	if (str[i] != '=')
69		return -EINVAL;
70	i++;
71	while (isspace(str[i]))
72		i++;
73	s = i;
74	if (isdigit(str[i]) || str[i] == '-') {
75		char *num, c;
76		int ret;
77
78		/* Make sure the field is not a string */
79		if (is_string_field(field))
80			return -EINVAL;
81
82		if (str[i] == '-')
83			i++;
84
85		/* We allow 0xDEADBEEF */
86		while (isalnum(str[i]))
87			i++;
88		num = str + s;
89		c = str[i];
90		if (c != '\0' && !isspace(c))
91			return -EINVAL;
92		str[i] = '\0';
93		/* Make sure it is a value */
94		if (field->is_signed)
95			ret = kstrtoll(num, 0, &val);
96		else
97			ret = kstrtoull(num, 0, &val);
98		str[i] = c;
99		if (ret)
100			return ret;
101
102		*pv = val;
103		return i;
104	} else if (str[i] == '\'' || str[i] == '"') {
105		char q = str[i];
106
107		/* Make sure the field is OK for strings */
108		if (!is_string_field(field))
109			return -EINVAL;
110
111		for (i++; str[i]; i++) {
112			if (str[i] == '\\' && str[i + 1]) {
113				i++;
114				continue;
115			}
116			if (str[i] == q)
117				break;
118		}
119		if (!str[i])
120			return -EINVAL;
121
122		/* Skip quotes */
123		s++;
124		len = i - s;
125		if (len >= MAX_FILTER_STR_VAL)
126			return -EINVAL;
127
128		*pv = (unsigned long)(str + s);
129		str[i] = 0;
130		/* go past the last quote */
131		i++;
132		return i;
133	}
134
135	return -EINVAL;
136}
137
138static int trace_get_entry_size(struct trace_event_call *call)
139{
140	struct ftrace_event_field *field;
141	struct list_head *head;
142	int size = 0;
143
144	head = trace_get_fields(call);
145	list_for_each_entry(field, head, link) {
146		if (field->size + field->offset > size)
147			size = field->size + field->offset;
148	}
149
150	return size;
151}
152
153static void *trace_alloc_entry(struct trace_event_call *call, int *size)
154{
155	int entry_size = trace_get_entry_size(call);
156	struct ftrace_event_field *field;
157	struct list_head *head;
158	void *entry = NULL;
159
160	/* We need an extra '\0' at the end. */
161	entry = kzalloc(entry_size + 1, GFP_KERNEL);
162	if (!entry)
163		return NULL;
164
165	head = trace_get_fields(call);
166	list_for_each_entry(field, head, link) {
167		if (!is_string_field(field))
168			continue;
169		if (field->filter_type == FILTER_STATIC_STRING)
170			continue;
171		if (field->filter_type == FILTER_DYN_STRING ||
172		    field->filter_type == FILTER_RDYN_STRING) {
173			u32 *str_item;
174			int str_loc = entry_size & 0xffff;
175
176			if (field->filter_type == FILTER_RDYN_STRING)
177				str_loc -= field->offset + field->size;
178
179			str_item = (u32 *)(entry + field->offset);
180			*str_item = str_loc; /* string length is 0. */
181		} else {
182			char **paddr;
183
184			paddr = (char **)(entry + field->offset);
185			*paddr = "";
186		}
187	}
188
189	*size = entry_size + 1;
190	return entry;
191}
192
193#define INJECT_STRING "STATIC STRING CAN NOT BE INJECTED"
194
195/* Caller is responsible to free the *pentry. */
196static int parse_entry(char *str, struct trace_event_call *call, void **pentry)
197{
198	struct ftrace_event_field *field;
199	void *entry = NULL;
200	int entry_size;
201	u64 val = 0;
202	int len;
203
204	entry = trace_alloc_entry(call, &entry_size);
205	*pentry = entry;
206	if (!entry)
207		return -ENOMEM;
208
209	tracing_generic_entry_update(entry, call->event.type,
210				     tracing_gen_ctx());
211
212	while ((len = parse_field(str, call, &field, &val)) > 0) {
213		if (is_function_field(field))
214			return -EINVAL;
215
216		if (is_string_field(field)) {
217			char *addr = (char *)(unsigned long) val;
218
219			if (field->filter_type == FILTER_STATIC_STRING) {
220				strscpy(entry + field->offset, addr, field->size);
221			} else if (field->filter_type == FILTER_DYN_STRING ||
222				   field->filter_type == FILTER_RDYN_STRING) {
223				int str_len = strlen(addr) + 1;
224				int str_loc = entry_size & 0xffff;
225				u32 *str_item;
226
227				entry_size += str_len;
228				*pentry = krealloc(entry, entry_size, GFP_KERNEL);
229				if (!*pentry) {
230					kfree(entry);
231					return -ENOMEM;
232				}
233				entry = *pentry;
234
235				strscpy(entry + (entry_size - str_len), addr, str_len);
236				str_item = (u32 *)(entry + field->offset);
237				if (field->filter_type == FILTER_RDYN_STRING)
238					str_loc -= field->offset + field->size;
239				*str_item = (str_len << 16) | str_loc;
240			} else {
241				char **paddr;
242
243				paddr = (char **)(entry + field->offset);
244				*paddr = INJECT_STRING;
245			}
246		} else {
247			switch (field->size) {
248			case 1: {
249				u8 tmp = (u8) val;
250
251				memcpy(entry + field->offset, &tmp, 1);
252				break;
253			}
254			case 2: {
255				u16 tmp = (u16) val;
256
257				memcpy(entry + field->offset, &tmp, 2);
258				break;
259			}
260			case 4: {
261				u32 tmp = (u32) val;
262
263				memcpy(entry + field->offset, &tmp, 4);
264				break;
265			}
266			case 8:
267				memcpy(entry + field->offset, &val, 8);
268				break;
269			default:
270				return -EINVAL;
271			}
272		}
273
274		str += len;
275	}
276
277	if (len < 0)
278		return len;
279
280	return entry_size;
281}
282
283static ssize_t
284event_inject_write(struct file *filp, const char __user *ubuf, size_t cnt,
285		   loff_t *ppos)
286{
287	struct trace_event_call *call;
288	struct trace_event_file *file;
289	int err = -ENODEV, size;
290	void *entry = NULL;
291	char *buf;
292
293	if (cnt >= PAGE_SIZE)
294		return -EINVAL;
295
296	buf = memdup_user_nul(ubuf, cnt);
297	if (IS_ERR(buf))
298		return PTR_ERR(buf);
299	strim(buf);
300
301	mutex_lock(&event_mutex);
302	file = event_file_data(filp);
303	if (file) {
304		call = file->event_call;
305		size = parse_entry(buf, call, &entry);
306		if (size < 0)
307			err = size;
308		else
309			err = trace_inject_entry(file, entry, size);
310	}
311	mutex_unlock(&event_mutex);
312
313	kfree(entry);
314	kfree(buf);
315
316	if (err < 0)
317		return err;
318
319	*ppos += err;
320	return cnt;
321}
322
323static ssize_t
324event_inject_read(struct file *file, char __user *buf, size_t size,
325		  loff_t *ppos)
326{
327	return -EPERM;
328}
329
330const struct file_operations event_inject_fops = {
331	.open = tracing_open_file_tr,
332	.read = event_inject_read,
333	.write = event_inject_write,
334	.release = tracing_release_file_tr,
335};
336