]>
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; | |
171 | if (field->filter_type == FILTER_DYN_STRING) { | |
172 | u32 *str_item; | |
173 | int str_loc = entry_size & 0xffff; | |
174 | ||
175 | str_item = (u32 *)(entry + field->offset); | |
176 | *str_item = str_loc; /* string length is 0. */ | |
177 | } else { | |
178 | char **paddr; | |
179 | ||
180 | paddr = (char **)(entry + field->offset); | |
181 | *paddr = ""; | |
182 | } | |
183 | } | |
184 | ||
185 | *size = entry_size + 1; | |
186 | return entry; | |
187 | } | |
188 | ||
189 | #define INJECT_STRING "STATIC STRING CAN NOT BE INJECTED" | |
190 | ||
191 | /* Caller is responsible to free the *pentry. */ | |
192 | static int parse_entry(char *str, struct trace_event_call *call, void **pentry) | |
193 | { | |
194 | struct ftrace_event_field *field; | |
195 | unsigned long irq_flags; | |
196 | void *entry = NULL; | |
197 | int entry_size; | |
198 | u64 val; | |
199 | int len; | |
200 | ||
201 | entry = trace_alloc_entry(call, &entry_size); | |
202 | *pentry = entry; | |
203 | if (!entry) | |
204 | return -ENOMEM; | |
205 | ||
206 | local_save_flags(irq_flags); | |
207 | tracing_generic_entry_update(entry, call->event.type, irq_flags, | |
208 | preempt_count()); | |
209 | ||
210 | while ((len = parse_field(str, call, &field, &val)) > 0) { | |
211 | if (is_function_field(field)) | |
212 | return -EINVAL; | |
213 | ||
214 | if (is_string_field(field)) { | |
215 | char *addr = (char *)(unsigned long) val; | |
216 | ||
217 | if (field->filter_type == FILTER_STATIC_STRING) { | |
218 | strlcpy(entry + field->offset, addr, field->size); | |
219 | } else if (field->filter_type == FILTER_DYN_STRING) { | |
220 | int str_len = strlen(addr) + 1; | |
221 | int str_loc = entry_size & 0xffff; | |
222 | u32 *str_item; | |
223 | ||
224 | entry_size += str_len; | |
225 | *pentry = krealloc(entry, entry_size, GFP_KERNEL); | |
226 | if (!*pentry) { | |
227 | kfree(entry); | |
228 | return -ENOMEM; | |
229 | } | |
230 | entry = *pentry; | |
231 | ||
232 | strlcpy(entry + (entry_size - str_len), addr, str_len); | |
233 | str_item = (u32 *)(entry + field->offset); | |
234 | *str_item = (str_len << 16) | str_loc; | |
235 | } else { | |
236 | char **paddr; | |
237 | ||
238 | paddr = (char **)(entry + field->offset); | |
239 | *paddr = INJECT_STRING; | |
240 | } | |
241 | } else { | |
242 | switch (field->size) { | |
243 | case 1: { | |
244 | u8 tmp = (u8) val; | |
245 | ||
246 | memcpy(entry + field->offset, &tmp, 1); | |
247 | break; | |
248 | } | |
249 | case 2: { | |
250 | u16 tmp = (u16) val; | |
251 | ||
252 | memcpy(entry + field->offset, &tmp, 2); | |
253 | break; | |
254 | } | |
255 | case 4: { | |
256 | u32 tmp = (u32) val; | |
257 | ||
258 | memcpy(entry + field->offset, &tmp, 4); | |
259 | break; | |
260 | } | |
261 | case 8: | |
262 | memcpy(entry + field->offset, &val, 8); | |
263 | break; | |
264 | default: | |
265 | return -EINVAL; | |
266 | } | |
267 | } | |
268 | ||
269 | str += len; | |
270 | } | |
271 | ||
272 | if (len < 0) | |
273 | return len; | |
274 | ||
275 | return entry_size; | |
276 | } | |
277 | ||
278 | static ssize_t | |
279 | event_inject_write(struct file *filp, const char __user *ubuf, size_t cnt, | |
280 | loff_t *ppos) | |
281 | { | |
282 | struct trace_event_call *call; | |
283 | struct trace_event_file *file; | |
284 | int err = -ENODEV, size; | |
285 | void *entry = NULL; | |
286 | char *buf; | |
287 | ||
288 | if (cnt >= PAGE_SIZE) | |
289 | return -EINVAL; | |
290 | ||
291 | buf = memdup_user_nul(ubuf, cnt); | |
292 | if (IS_ERR(buf)) | |
293 | return PTR_ERR(buf); | |
294 | strim(buf); | |
295 | ||
296 | mutex_lock(&event_mutex); | |
297 | file = event_file_data(filp); | |
298 | if (file) { | |
299 | call = file->event_call; | |
300 | size = parse_entry(buf, call, &entry); | |
301 | if (size < 0) | |
302 | err = size; | |
303 | else | |
304 | err = trace_inject_entry(file, entry, size); | |
305 | } | |
306 | mutex_unlock(&event_mutex); | |
307 | ||
308 | kfree(entry); | |
309 | kfree(buf); | |
310 | ||
311 | if (err < 0) | |
312 | return err; | |
313 | ||
314 | *ppos += err; | |
315 | return cnt; | |
316 | } | |
317 | ||
318 | static ssize_t | |
319 | event_inject_read(struct file *file, char __user *buf, size_t size, | |
320 | loff_t *ppos) | |
321 | { | |
322 | return -EPERM; | |
323 | } | |
324 | ||
325 | const struct file_operations event_inject_fops = { | |
326 | .open = tracing_open_generic, | |
327 | .read = event_inject_read, | |
328 | .write = event_inject_write, | |
329 | }; |