Commit | Line | Data |
---|---|---|
6c3edaf9 CW |
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 | ||
16 | static int | |
17 | trace_inject_entry(struct trace_event_file *file, void *rec, int len) | |
18 | { | |
19 | struct trace_event_buffer fbuffer; | |
6c3edaf9 CW |
20 | int written = 0; |
21 | void *entry; | |
22 | ||
23 | rcu_read_lock_sched(); | |
6c3edaf9 CW |
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 | ||
35 | static int | |
36 | parse_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 | ||
138 | static 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 | ||
153 | static 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; | |
05770dd0 MH |
171 | if (field->filter_type == FILTER_DYN_STRING || |
172 | field->filter_type == FILTER_RDYN_STRING) { | |
6c3edaf9 CW |
173 | u32 *str_item; |
174 | int str_loc = entry_size & 0xffff; | |
175 | ||
05770dd0 MH |
176 | if (field->filter_type == FILTER_RDYN_STRING) |
177 | str_loc -= field->offset + field->size; | |
178 | ||
6c3edaf9 CW |
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. */ | |
196 | static int parse_entry(char *str, struct trace_event_call *call, void **pentry) | |
197 | { | |
198 | struct ftrace_event_field *field; | |
6c3edaf9 CW |
199 | void *entry = NULL; |
200 | int entry_size; | |
02f4e01c | 201 | u64 val = 0; |
6c3edaf9 CW |
202 | int len; |
203 | ||
204 | entry = trace_alloc_entry(call, &entry_size); | |
205 | *pentry = entry; | |
206 | if (!entry) | |
207 | return -ENOMEM; | |
208 | ||
36590c50 SAS |
209 | tracing_generic_entry_update(entry, call->event.type, |
210 | tracing_gen_ctx()); | |
6c3edaf9 CW |
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 | strlcpy(entry + field->offset, addr, field->size); | |
05770dd0 MH |
221 | } else if (field->filter_type == FILTER_DYN_STRING || |
222 | field->filter_type == FILTER_RDYN_STRING) { | |
6c3edaf9 CW |
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 | strlcpy(entry + (entry_size - str_len), addr, str_len); | |
236 | str_item = (u32 *)(entry + field->offset); | |
05770dd0 MH |
237 | if (field->filter_type == FILTER_RDYN_STRING) |
238 | str_loc -= field->offset + field->size; | |
6c3edaf9 CW |
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 | ||
283 | static ssize_t | |
284 | event_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 | ||
323 | static ssize_t | |
324 | event_inject_read(struct file *file, char __user *buf, size_t size, | |
325 | loff_t *ppos) | |
326 | { | |
327 | return -EPERM; | |
328 | } | |
329 | ||
330 | const struct file_operations event_inject_fops = { | |
331 | .open = tracing_open_generic, | |
332 | .read = event_inject_read, | |
333 | .write = event_inject_write, | |
334 | }; |