3 * Support for attaching to the runtime from other processes.
6 * Zoltan Varga (vargaz@gmail.com)
8 * Copyright 2007-2009 Novell, Inc (http://www.novell.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
17 #define DISABLE_ATTACH
19 #ifndef DISABLE_ATTACH
24 #include <sys/types.h>
25 #include <sys/socket.h>
28 #include <netinet/in.h>
36 #include <mono/metadata/assembly-internals.h>
37 #include <mono/metadata/metadata.h>
38 #include <mono/metadata/class-internals.h>
39 #include <mono/metadata/object-internals.h>
40 #include <mono/metadata/threads-types.h>
41 #include <mono/metadata/gc-internals.h>
42 #include <mono/utils/mono-threads.h>
44 #include <mono/utils/w32api.h>
47 * This module enables other processes to attach to a running mono process and
48 * load agent assemblies.
49 * Communication is done through a UNIX Domain Socket located at
50 * /tmp/mono-<USER>/.mono-<PID>.
51 * We use a simplified version of the .net remoting protocol.
52 * To increase security, and to avoid spinning up a listener thread on startup,
53 * we follow the java implementation, and only start up the attach mechanism
54 * when we receive a QUIT signal and there is a file named
55 * '.mono_attach_pid<PID>' in /tmp.
58 * - This module allows loading of arbitrary code into a running mono runtime, so
59 * it is security critical.
60 * - Security is based on controlling access to the unix file to which the unix
61 * domain socket is bound. Permissions/ownership are set such that only the owner
62 * of the process can access the socket.
63 * - As an additional measure, the socket is only created when the process receives
64 * a SIGQUIT signal, which only its owner/root can send.
65 * - The socket is kept in a directory whose ownership is checked before creating
66 * the socket. This could allow an attacker a kind of DOS attack by creating the
67 * directory with the wrong permissions/ownership. However, the only thing such
68 * an attacker could prevent is the attaching of agents to the mono runtime.
79 /*******************************************************************/
80 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
81 /*******************************************************************/
90 static AgentConfig config
;
92 static int listen_fd
, conn_fd
;
94 static char *ipc_filename
;
96 static char *server_uri
;
98 static MonoThreadHandle
*receiver_thread_handle
;
100 static volatile gboolean stop_receiver_thread
;
102 static gboolean needs_to_start
, started
;
104 static void transport_connect (void);
106 static gsize WINAPI
receiver_thread (void *arg
);
108 static void transport_start_receive (void);
111 * Functions to decode protocol data
114 decode_byte (guint8
const *buf
, guint8
const **endbuf
, guint8
const *limit
)
117 g_assert (*endbuf
<= limit
);
122 decode_int (const guint8
*buf
)
124 return (((int)buf
[0]) << 0) | (((int)buf
[1]) << 8) | (((int)buf
[2]) << 16) | (((int)buf
[3]) << 24);
128 decode_string_value (guint8
const *buf
, guint8
const **endbuf
, guint8
const *limit
)
132 guint8
const *p
= buf
;
134 type
= decode_byte (p
, &p
, limit
);
135 if (type
== PRIM_TYPE_NULL
) {
139 g_assert (type
== PRIM_TYPE_STRING
);
143 guint8 b
= decode_byte (p
, &p
, limit
);
151 g_assert (length
< (1 << 16));
153 const char *s
= (const char*)p
;
156 g_assert (p
<= limit
);
163 /********************************/
164 /* AGENT IMPLEMENTATION */
165 /********************************/
168 mono_attach_parse_options (char *options
)
172 if (!strcmp (options
, "disable"))
173 config
.enabled
= FALSE
;
177 mono_attach_init (void)
179 config
.enabled
= TRUE
;
185 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
187 * Returns: whenever it was started.
190 mono_attach_start (void)
198 /* Check for the existence of the trigger file */
201 * We don't do anything with this file, and the only thing an attacker can do
202 * by creating it is to enable the attach mechanism if the process receives a
203 * SIGQUIT signal, which can only be sent by the owner/root.
205 snprintf (path
, sizeof (path
), "/tmp/.mono_attach_pid%" PRIdMAX
, (intmax_t) getpid ());
206 fd
= open (path
, O_RDONLY
);
212 /* Act like we started */
219 * Our startup includes non signal-safe code, so ask the finalizer thread to
220 * do the actual startup.
222 needs_to_start
= TRUE
;
223 mono_gc_finalize_notify ();
228 /* Called by the finalizer thread when it is woken up */
230 mono_attach_maybe_start (void)
235 needs_to_start
= FALSE
;
237 transport_start_receive ();
244 mono_attach_cleanup (void)
249 unlink (ipc_filename
);
251 stop_receiver_thread
= TRUE
;
253 /* This will cause receiver_thread () to break out of the read () call */
256 /* Wait for the receiver thread to exit */
257 if (receiver_thread_handle
)
258 mono_thread_info_wait_one_handle (receiver_thread_handle
, 0, FALSE
);
262 mono_attach_load_agent (MonoDomain
*domain
, const char *agent
, const char *args
)
264 HANDLE_FUNCTION_ENTER ();
267 MonoAssembly
*agent_assembly
;
271 MonoArrayHandle main_args
;
273 MonoImageOpenStatus open_status
;
276 MonoAssemblyOpenRequest req
;
277 mono_assembly_request_prepare_open (&req
, MONO_ASMCTX_DEFAULT
, mono_domain_default_alc (mono_domain_get ()));
278 agent_assembly
= mono_assembly_request_open (agent
, &req
, &open_status
);
279 if (!agent_assembly
) {
280 fprintf (stderr
, "Cannot open agent assembly '%s': %s.\n", agent
, mono_image_strerror (open_status
));
286 * Can't use mono_jit_exec (), as it sets things which might confuse the
289 image
= mono_assembly_get_image_internal (agent_assembly
);
290 entry
= mono_image_get_entry_point (image
);
292 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image
));
297 method
= mono_get_method_checked (image
, entry
, NULL
, NULL
, error
);
299 g_print ("The entry point method of assembly '%s' could not be loaded due to %s\n", agent
, mono_error_get_message (error
));
304 main_args
= mono_array_new_handle (domain
, mono_defaults
.string_class
, (args
== NULL
) ? 0 : 1, error
);
305 if (MONO_HANDLE_IS_NULL (main_args
)) {
306 g_print ("Could not allocate main method args due to %s\n", mono_error_get_message (error
));
312 MonoStringHandle args_str
= mono_string_new_handle (domain
, args
, error
);
313 if (!is_ok (error
)) {
314 g_print ("Could not allocate main method arg string due to %s\n", mono_error_get_message (error
));
318 MONO_HANDLE_ARRAY_SETREF (main_args
, 0, args_str
);
321 pa
[0] = MONO_HANDLE_RAW (main_args
);
323 mono_runtime_try_invoke (method
, NULL
, pa
, &exc
, error
);
324 if (!is_ok (error
)) {
325 g_print ("The entry point method of assembly '%s' could not be executed due to %s\n", agent
, mono_error_get_message (error
));
332 mono_error_cleanup (error
);
333 HANDLE_FUNCTION_RETURN_VAL (result
);
339 * Create a UNIX domain socket and bind it to a file in /tmp.
341 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
342 * permissions system to prevent attackers from connecting to the socket.
347 struct sockaddr_un name
;
350 char *filename
, *directory
;
356 if (getuid () != geteuid ()) {
357 fprintf (stderr
, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
361 /* Create the socket. */
362 sock
= socket (PF_UNIX
, SOCK_STREAM
, 0);
364 perror ("attach: failed to create IPC socket");
369 * For security reasons, create a directory to hold the listening socket,
370 * since there is a race between bind () and chmod () below.
372 /* FIXME: Use TMP ? */
374 #ifdef HAVE_GETPWUID_R
375 res
= getpwuid_r (getuid (), &pwbuf
, buf
, sizeof (buf
), &pw
);
377 pw
= getpwuid(getuid ());
378 res
= pw
!= NULL
? 0 : 1;
381 fprintf (stderr
, "attach: getpwuid_r () failed.\n");
385 directory
= g_strdup_printf ("/tmp/mono-%s", pw
->pw_name
);
386 res
= mkdir (directory
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
388 if (errno
== EEXIST
) {
389 /* Check type and permissions */
390 res
= lstat (directory
, &stat
);
392 perror ("attach: lstat () failed");
395 if (!S_ISDIR (stat
.st_mode
)) {
396 fprintf (stderr
, "attach: path '%s' is not a directory.\n", directory
);
399 if (stat
.st_uid
!= getuid ()) {
400 fprintf (stderr
, "attach: directory '%s' is not owned by the current user.\n", directory
);
403 if ((stat
.st_mode
& S_IRWXG
) != 0 || (stat
.st_mode
& S_IRWXO
) || ((stat
.st_mode
& S_IRWXU
) != (S_IRUSR
| S_IWUSR
| S_IXUSR
))) {
404 fprintf (stderr
, "attach: directory '%s' should have protection 0700.\n", directory
);
408 perror ("attach: mkdir () failed");
413 filename
= g_strdup_printf ("%s/.mono-%" PRIdMAX
, directory
, (intmax_t) getpid ());
416 /* Bind a name to the socket. */
417 name
.sun_family
= AF_UNIX
;
418 strcpy (name
.sun_path
, filename
);
420 size
= (offsetof (struct sockaddr_un
, sun_path
)
421 + strlen (name
.sun_path
) + 1);
423 if (bind (sock
, (struct sockaddr
*) &name
, size
) < 0) {
424 fprintf (stderr
, "attach: failed to bind IPC socket '%s': %s\n", filename
, strerror (errno
));
429 /* Set permissions */
430 res
= chmod (filename
, S_IRUSR
| S_IWUSR
);
432 perror ("attach: failed to set permissions on IPC socket");
438 res
= listen (sock
, 16);
440 fprintf (stderr
, "attach: listen () failed: %s\n", strerror (errno
));
446 ipc_filename
= g_strdup (filename
);
448 server_uri
= g_strdup_printf ("unix://%s/.mono-%" PRIdMAX
"?/vm", directory
, (intmax_t) getpid ());
455 transport_connect (void)
463 transport_send (int fd
, guint8
*data
, int len
)
467 stats
.bytes_sent
+= len
;
468 //printf ("X: %d\n", stats.bytes_sent);
470 res
= write (fd
, data
, len
);
472 /* FIXME: What to do here ? */
479 transport_start_receive (void)
482 MonoInternalThread
*internal
;
484 transport_connect ();
489 internal
= mono_thread_create_internal (mono_get_root_domain (), (gpointer
)receiver_thread
, NULL
, MONO_THREAD_CREATE_FLAGS_NONE
, error
);
490 mono_error_assert_ok (error
);
492 receiver_thread_handle
= mono_threads_open_thread_handle (internal
->handle
);
493 g_assert (receiver_thread_handle
);
497 receiver_thread (void *arg
)
499 MonoInternalThread
*internal
= mono_thread_internal_current ();
501 mono_thread_set_name_constant_ignore_error (internal
, "Attach receiver", MonoSetThreadNameFlag_Permanent
);
503 /* Ask the runtime to not abort this thread */
504 //internal->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
505 /* Ask the runtime to not wait for this thread */
506 internal
->state
|= ThreadState_Background
;
508 printf ("attach: Listening on '%s'...\n", server_uri
);
511 conn_fd
= accept (listen_fd
, NULL
, NULL
);
513 /* Probably closed by mono_attach_cleanup () */
516 printf ("attach: Connected.\n");
524 int res
= read (conn_fd
, buffer
, 6);
526 if (res
== -1 && errno
== EINTR
)
529 if (res
== -1 || stop_receiver_thread
)
535 if (memcmp (buffer
, "MONO", 4) != 0 || buffer
[4] != 1 || buffer
[5] != 0) {
536 fprintf (stderr
, "attach: message from server has unknown header.\n");
540 /* Read content length */
541 res
= read (conn_fd
, buffer
, 4);
545 const int content_len
= decode_int (buffer
);
547 /* Read message body */
548 body
= (guint8
*)g_malloc (content_len
);
549 res
= read (conn_fd
, body
, content_len
);
550 if (res
!= content_len
)
553 guint8
const * p
= body
;
554 guint8
const * const p_end
= body
+ content_len
;
556 char const * const cmd
= decode_string_value (p
, &p
, p_end
);
560 // 10: 7:attach\0 + one byte each for the types of cmd, name, args.
561 g_assert (content_len
>= 10 && !memcmp (cmd
, "attach", 7));
563 char const * const agent_name
= decode_string_value (p
, &p
, p_end
);
564 char const * const agent_args
= decode_string_value (p
, &p
, p_end
);
566 printf ("attach: Loading agent '%s'.\n", agent_name
);
567 mono_attach_load_agent (mono_domain_get (), agent_name
, agent_args
);
572 // FIXME: Send back a result
581 printf ("attach: Disconnected.\n");
583 if (stop_receiver_thread
)
590 #else /* DISABLE_ATTACH */
593 mono_attach_parse_options (char *options
)
598 mono_attach_init (void)
603 mono_attach_start (void)
609 mono_attach_maybe_start (void)
614 mono_attach_cleanup (void)
618 #endif /* DISABLE_ATTACH */