]>
Commit | Line | Data |
---|---|---|
2d21ac55 A |
1 | /* |
2 | * CDDL HEADER START | |
3 | * | |
4 | * The contents of this file are subject to the terms of the | |
5 | * Common Development and Distribution License (the "License"). | |
6 | * You may not use this file except in compliance with the License. | |
7 | * | |
8 | * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE | |
9 | * or http://www.opensolaris.org/os/licensing. | |
10 | * See the License for the specific language governing permissions | |
11 | * and limitations under the License. | |
12 | * | |
13 | * When distributing Covered Code, include this CDDL HEADER in each | |
14 | * file and include the License file at usr/src/OPENSOLARIS.LICENSE. | |
15 | * If applicable, add the following below this CDDL HEADER, with the | |
16 | * fields enclosed by brackets "[]" replaced with your own identifying | |
17 | * information: Portions Copyright [yyyy] [name of copyright owner] | |
18 | * | |
19 | * CDDL HEADER END | |
20 | */ | |
21 | ||
22 | /* | |
b0d623f7 | 23 | * Copyright 2007 Sun Microsystems, Inc. All rights reserved. |
2d21ac55 A |
24 | * Use is subject to license terms. |
25 | */ | |
26 | ||
27 | /* | |
b0d623f7 | 28 | * #pragma ident "@(#)dtrace_subr.c 1.8 07/06/05 SMI" |
2d21ac55 A |
29 | */ |
30 | ||
31 | #include <stdarg.h> | |
32 | #include <string.h> | |
33 | #include <sys/malloc.h> | |
34 | #include <sys/time.h> | |
35 | #include <sys/dtrace.h> | |
36 | #include <sys/dtrace_impl.h> | |
fe8ab488 | 37 | #include <sys/proc_internal.h> |
3e170ce0 | 38 | #include <sys/vnode.h> |
2d21ac55 | 39 | #include <kern/debug.h> |
fe8ab488 A |
40 | #include <kern/sched_prim.h> |
41 | #include <kern/task.h> | |
2d21ac55 | 42 | |
fe8ab488 A |
43 | #if CONFIG_CSR |
44 | #include <sys/codesign.h> | |
45 | #include <sys/csr.h> | |
2d21ac55 A |
46 | #endif |
47 | ||
fe8ab488 A |
48 | /* |
49 | * APPLE NOTE: Solaris proc_t is the struct. | |
50 | * Darwin's proc_t is a pointer to it. | |
51 | */ | |
52 | #define proc_t struct proc /* Steer clear of the Darwin typedef for proc_t */ | |
53 | ||
54 | ||
2d21ac55 A |
55 | /* Copied from an arch specific dtrace_subr.c. */ |
56 | int (*dtrace_fasttrap_probe_ptr)(struct regs *); | |
57 | ||
58 | /* | |
59 | * Following DTrace hooks are taken from Solaris' dtrace_subr.c | |
60 | * They're assigned in dtrace.c but Darwin never calls them. | |
61 | */ | |
62 | void (*dtrace_cpu_init)(processorid_t); | |
316670eb | 63 | int (*dtrace_modload)(struct kmod_info *, uint32_t); |
6d2010ae | 64 | int (*dtrace_modunload)(struct kmod_info *); |
2d21ac55 | 65 | void (*dtrace_helpers_cleanup)(proc_t *); |
2d21ac55 A |
66 | void (*dtrace_helpers_fork)(proc_t *, proc_t *); |
67 | void (*dtrace_cpustart_init)(void); | |
68 | void (*dtrace_cpustart_fini)(void); | |
69 | ||
2d21ac55 A |
70 | void (*dtrace_debugger_init)(void); |
71 | void (*dtrace_debugger_fini)(void); | |
72 | ||
73 | dtrace_vtime_state_t dtrace_vtime_active = 0; | |
74 | dtrace_cacheid_t dtrace_predcache_id = DTRACE_CACHEIDNONE + 1; | |
75 | ||
76 | void (*dtrace_fasttrap_fork_ptr)(proc_t *, proc_t *); | |
77 | void (*dtrace_fasttrap_exec_ptr)(proc_t *); | |
78 | void (*dtrace_fasttrap_exit_ptr)(proc_t *); | |
79 | ||
80 | /* | |
81 | * This function is called by cfork() in the event that it appears that | |
82 | * there may be dtrace tracepoints active in the parent process's address | |
83 | * space. This first confirms the existence of dtrace tracepoints in the | |
84 | * parent process and calls into the fasttrap module to remove the | |
85 | * corresponding tracepoints from the child. By knowing that there are | |
86 | * existing tracepoints, and ensuring they can't be removed, we can rely | |
87 | * on the fasttrap module remaining loaded. | |
88 | */ | |
89 | void | |
90 | dtrace_fasttrap_fork(proc_t *p, proc_t *cp) | |
91 | { | |
2d21ac55 A |
92 | if (dtrace_fasttrap_fork_ptr) { |
93 | (*dtrace_fasttrap_fork_ptr)(p, cp); | |
94 | } | |
95 | } | |
96 | ||
fe8ab488 A |
97 | |
98 | /* | |
99 | * DTrace wait for process execution | |
100 | * | |
101 | * This feature is using a list of entries, each entry containing a pointer | |
102 | * on a process description. The description is provided by a client, and it | |
103 | * contains the command we want to wait for along with a reserved space for | |
104 | * the caught process id. | |
105 | * | |
106 | * Once an awaited process has been spawned, it will be suspended before | |
107 | * notifying the client. Once the client has been back to userland, it's its | |
108 | * duty to resume the task. | |
109 | */ | |
110 | ||
111 | lck_mtx_t dtrace_procwaitfor_lock; | |
112 | ||
113 | typedef struct dtrace_proc_awaited_entry { | |
114 | struct dtrace_procdesc *pdesc; | |
115 | LIST_ENTRY(dtrace_proc_awaited_entry) entries; | |
116 | } dtrace_proc_awaited_entry_t; | |
117 | ||
118 | LIST_HEAD(listhead, dtrace_proc_awaited_entry) dtrace_proc_awaited_head | |
119 | = LIST_HEAD_INITIALIZER(dtrace_proc_awaited_head); | |
120 | ||
121 | void (*dtrace_proc_waitfor_exec_ptr)(proc_t*) = NULL; | |
122 | ||
3e170ce0 A |
123 | static int |
124 | dtrace_proc_get_execpath(proc_t *p, char *buffer, int *maxlen) | |
125 | { | |
126 | int err = 0, vid = 0; | |
127 | vnode_t tvp = NULLVP, nvp = NULLVP; | |
128 | ||
129 | ASSERT(p); | |
130 | ASSERT(buffer); | |
131 | ASSERT(maxlen); | |
132 | ||
133 | if ((tvp = p->p_textvp) == NULLVP) | |
134 | return ESRCH; | |
135 | ||
136 | vid = vnode_vid(tvp); | |
137 | if ((err = vnode_getwithvid(tvp, vid)) != 0) | |
138 | return err; | |
139 | ||
140 | if ((err = vn_getpath_fsenter(tvp, buffer, maxlen)) != 0) | |
141 | return err; | |
142 | vnode_put(tvp); | |
143 | ||
144 | if ((err = vnode_lookup(buffer, 0, &nvp, vfs_context_current())) != 0) | |
145 | return err; | |
146 | if (nvp != NULLVP) | |
147 | vnode_put(nvp); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | ||
fe8ab488 A |
153 | static void |
154 | dtrace_proc_exec_notification(proc_t *p) { | |
155 | dtrace_proc_awaited_entry_t *entry, *tmp; | |
3e170ce0 | 156 | static char execpath[MAXPATHLEN]; |
fe8ab488 A |
157 | |
158 | ASSERT(p); | |
159 | ASSERT(p->p_pid != -1); | |
160 | ASSERT(current_task() != p->task); | |
161 | ||
162 | lck_mtx_lock(&dtrace_procwaitfor_lock); | |
163 | ||
fe8ab488 | 164 | LIST_FOREACH_SAFE(entry, &dtrace_proc_awaited_head, entries, tmp) { |
3e170ce0 A |
165 | /* By default consider we're using p_comm. */ |
166 | char *pname = p->p_comm; | |
167 | ||
168 | /* Already matched with another process. */ | |
169 | if ((entry->pdesc->p_pid != -1)) | |
170 | continue; | |
171 | ||
172 | /* p_comm is too short, use the execpath. */ | |
173 | if (entry->pdesc->p_name_length >= MAXCOMLEN) { | |
174 | /* | |
175 | * Retrieve the executable path. After the call, length contains | |
176 | * the length of the string + 1. | |
177 | */ | |
178 | int length = sizeof(execpath); | |
179 | if (dtrace_proc_get_execpath(p, execpath, &length) != 0) | |
180 | continue; | |
181 | /* Move the cursor to the position after the last / */ | |
182 | pname = &execpath[length - 1]; | |
183 | while (pname != execpath && *pname != '/') | |
184 | pname--; | |
185 | pname = (*pname == '/') ? pname + 1 : pname; | |
186 | } | |
187 | ||
188 | if (!strcmp(entry->pdesc->p_name, pname)) { | |
fe8ab488 A |
189 | entry->pdesc->p_pid = p->p_pid; |
190 | task_pidsuspend(p->task); | |
191 | wakeup(entry); | |
192 | } | |
193 | } | |
194 | ||
195 | lck_mtx_unlock(&dtrace_procwaitfor_lock); | |
196 | } | |
197 | ||
198 | int | |
199 | dtrace_proc_waitfor(dtrace_procdesc_t* pdesc) { | |
200 | dtrace_proc_awaited_entry_t entry; | |
201 | int res; | |
202 | ||
203 | ASSERT(pdesc); | |
3e170ce0 A |
204 | ASSERT(pdesc->p_name); |
205 | ||
206 | /* | |
207 | * Never trust user input, compute the length of the process name and ensure the | |
208 | * string is null terminated. | |
209 | */ | |
210 | pdesc->p_name_length = strnlen(pdesc->p_name, sizeof(pdesc->p_name)); | |
211 | if (pdesc->p_name_length >= (int) sizeof(pdesc->p_name)) | |
212 | return -1; | |
fe8ab488 A |
213 | |
214 | lck_mtx_lock(&dtrace_procwaitfor_lock); | |
215 | ||
216 | /* Initialize and insert the entry, then install the hook. */ | |
217 | pdesc->p_pid = -1; | |
218 | entry.pdesc = pdesc; | |
219 | LIST_INSERT_HEAD(&dtrace_proc_awaited_head, &entry, entries); | |
220 | dtrace_proc_waitfor_exec_ptr = &dtrace_proc_exec_notification; | |
221 | ||
222 | /* Sleep until the process has been executed */ | |
223 | res = msleep(&entry, &dtrace_procwaitfor_lock, PCATCH, "dtrace_proc_waitfor", NULL); | |
224 | ||
225 | /* Remove the entry and the hook if it is not needed anymore. */ | |
226 | LIST_REMOVE(&entry, entries); | |
227 | if (LIST_EMPTY(&dtrace_proc_awaited_head)) | |
228 | dtrace_proc_waitfor_exec_ptr = NULL; | |
229 | ||
230 | lck_mtx_unlock(&dtrace_procwaitfor_lock); | |
231 | ||
232 | return res; | |
233 | } | |
234 | ||
235 | ||
2d21ac55 A |
236 | typedef struct dtrace_invop_hdlr { |
237 | int (*dtih_func)(uintptr_t, uintptr_t *, uintptr_t); | |
238 | struct dtrace_invop_hdlr *dtih_next; | |
239 | } dtrace_invop_hdlr_t; | |
240 | ||
241 | dtrace_invop_hdlr_t *dtrace_invop_hdlr; | |
242 | ||
243 | int | |
244 | dtrace_invop(uintptr_t, uintptr_t *, uintptr_t); | |
245 | ||
246 | int | |
247 | dtrace_invop(uintptr_t addr, uintptr_t *stack, uintptr_t eax) | |
248 | { | |
249 | dtrace_invop_hdlr_t *hdlr; | |
250 | int rval; | |
251 | ||
252 | for (hdlr = dtrace_invop_hdlr; hdlr != NULL; hdlr = hdlr->dtih_next) { | |
253 | if ((rval = hdlr->dtih_func(addr, stack, eax)) != 0) | |
254 | return (rval); | |
255 | } | |
256 | ||
257 | return (0); | |
258 | } | |
259 | ||
260 | void | |
261 | dtrace_invop_add(int (*func)(uintptr_t, uintptr_t *, uintptr_t)) | |
262 | { | |
263 | dtrace_invop_hdlr_t *hdlr; | |
264 | ||
265 | hdlr = kmem_alloc(sizeof (dtrace_invop_hdlr_t), KM_SLEEP); | |
266 | hdlr->dtih_func = func; | |
267 | hdlr->dtih_next = dtrace_invop_hdlr; | |
268 | dtrace_invop_hdlr = hdlr; | |
269 | } | |
270 | ||
271 | void | |
272 | dtrace_invop_remove(int (*func)(uintptr_t, uintptr_t *, uintptr_t)) | |
273 | { | |
274 | dtrace_invop_hdlr_t *hdlr = dtrace_invop_hdlr, *prev = NULL; | |
275 | ||
276 | for (;;) { | |
277 | if (hdlr == NULL) | |
278 | panic("attempt to remove non-existent invop handler"); | |
279 | ||
280 | if (hdlr->dtih_func == func) | |
281 | break; | |
282 | ||
283 | prev = hdlr; | |
284 | hdlr = hdlr->dtih_next; | |
285 | } | |
286 | ||
287 | if (prev == NULL) { | |
288 | ASSERT(dtrace_invop_hdlr == hdlr); | |
289 | dtrace_invop_hdlr = hdlr->dtih_next; | |
290 | } else { | |
291 | ASSERT(dtrace_invop_hdlr != hdlr); | |
292 | prev->dtih_next = hdlr->dtih_next; | |
293 | } | |
294 | ||
295 | kmem_free(hdlr, sizeof (dtrace_invop_hdlr_t)); | |
296 | } | |
297 | ||
39037602 A |
298 | static minor_t next_minor = 0; |
299 | static dtrace_state_t* dtrace_clients[DTRACE_NCLIENTS] = {NULL}; | |
300 | ||
301 | ||
302 | minor_t | |
303 | dtrace_state_reserve(void) | |
304 | { | |
305 | for (int i = 0; i < DTRACE_NCLIENTS; i++) { | |
306 | minor_t minor = atomic_add_32(&next_minor, 1) % DTRACE_NCLIENTS; | |
307 | if (dtrace_clients[minor] == NULL) | |
308 | return minor; | |
309 | } | |
310 | return 0; | |
311 | } | |
312 | ||
313 | dtrace_state_t* | |
314 | dtrace_state_get(minor_t minor) | |
315 | { | |
316 | ASSERT(minor < DTRACE_NCLIENTS); | |
317 | return dtrace_clients[minor]; | |
318 | } | |
319 | ||
320 | dtrace_state_t* | |
321 | dtrace_state_allocate(minor_t minor) | |
322 | { | |
323 | dtrace_state_t *state = _MALLOC(sizeof(dtrace_state_t), M_TEMP, M_ZERO | M_WAITOK); | |
324 | if (dtrace_casptr(&dtrace_clients[minor], NULL, state) != NULL) { | |
325 | // We have been raced by another client for this number, abort | |
326 | _FREE(state, M_TEMP); | |
327 | return NULL; | |
328 | } | |
329 | return state; | |
330 | } | |
331 | ||
332 | void | |
333 | dtrace_state_free(minor_t minor) | |
334 | { | |
335 | dtrace_state_t *state = dtrace_clients[minor]; | |
336 | dtrace_clients[minor] = NULL; | |
337 | _FREE(state, M_TEMP); | |
338 | } | |
3e170ce0 A |
339 | |
340 | ||
341 | ||
342 | void | |
343 | dtrace_restriction_policy_load(void) | |
344 | { | |
345 | } | |
346 | ||
fe8ab488 A |
347 | /* |
348 | * Check if DTrace has been restricted by the current security policy. | |
349 | */ | |
350 | boolean_t | |
351 | dtrace_is_restricted(void) | |
352 | { | |
353 | #if CONFIG_CSR | |
354 | if (csr_check(CSR_ALLOW_UNRESTRICTED_DTRACE) != 0) | |
355 | return TRUE; | |
356 | #endif | |
357 | ||
358 | return FALSE; | |
359 | } | |
360 | ||
3e170ce0 | 361 | boolean_t |
39037602 | 362 | dtrace_are_restrictions_relaxed(void) |
3e170ce0 A |
363 | { |
364 | #if CONFIG_CSR | |
365 | if (csr_check(CSR_ALLOW_APPLE_INTERNAL) == 0) | |
366 | return TRUE; | |
367 | #endif | |
368 | ||
369 | return FALSE; | |
370 | } | |
371 | ||
372 | boolean_t | |
373 | dtrace_fbt_probes_restricted(void) | |
374 | { | |
375 | ||
376 | #if CONFIG_CSR | |
39037602 | 377 | if (dtrace_is_restricted() && !dtrace_are_restrictions_relaxed()) |
3e170ce0 A |
378 | return TRUE; |
379 | #endif | |
380 | ||
381 | return FALSE; | |
382 | } | |
383 | ||
39037602 A |
384 | boolean_t |
385 | dtrace_sdt_probes_restricted(void) | |
386 | { | |
387 | ||
388 | return FALSE; | |
389 | } | |
390 | ||
fe8ab488 A |
391 | /* |
392 | * Check if the process can be attached. | |
393 | */ | |
394 | boolean_t | |
395 | dtrace_can_attach_to_proc(proc_t *proc) | |
396 | { | |
397 | #pragma unused(proc) | |
398 | ASSERT(proc != NULL); | |
399 | ||
400 | #if CONFIG_CSR | |
3e170ce0 | 401 | if (cs_restricted(proc)) |
fe8ab488 A |
402 | return FALSE; |
403 | #endif | |
404 | ||
405 | return TRUE; | |
406 | } | |
407 |