2 * attach.c: Support for attaching to the runtime from other processes.
5 * Zoltan Varga (vargaz@gmail.com)
7 * Copyright 2007-2009 Novell, Inc (http://www.novell.com)
14 #define DISABLE_ATTACH
16 #ifndef DISABLE_ATTACH
21 #include <sys/types.h>
22 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <sys/types.h>
35 #include <mono/metadata/assembly.h>
36 #include <mono/metadata/metadata.h>
37 #include <mono/metadata/class-internals.h>
38 #include <mono/metadata/object-internals.h>
39 #include <mono/metadata/threads-types.h>
40 #include <mono/metadata/gc-internal.h>
44 * This module enables other processes to attach to a running mono process and
45 * load agent assemblies.
46 * Communication is done through a UNIX Domain Socket located at
47 * /tmp/mono-<USER>/.mono-<PID>.
48 * We use a simplified version of the .net remoting protocol.
49 * To increase security, and to avoid spinning up a listener thread on startup,
50 * we follow the java implementation, and only start up the attach mechanism
51 * when we receive a QUIT signal and there is a file named
52 * '.mono_attach_pid<PID>' in /tmp.
55 * - This module allows loading of arbitrary code into a running mono runtime, so
56 * it is security critical.
57 * - Security is based on controlling access to the unix file to which the unix
58 * domain socket is bound. Permissions/ownership are set such that only the owner
59 * of the process can access the socket.
60 * - As an additional measure, the socket is only created when the process receives
61 * a SIGQUIT signal, which only its owner/root can send.
62 * - The socket is kept in a directory whose ownership is checked before creating
63 * the socket. This could allow an attacker a kind of DOS attack by creating the
64 * directory with the wrong permissions/ownership. However, the only thing such
65 * an attacker could prevent is the attaching of agents to the mono runtime.
76 /*******************************************************************/
77 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
78 /*******************************************************************/
87 static AgentConfig config
;
89 static int listen_fd
, conn_fd
;
91 static char *ipc_filename
;
93 static char *server_uri
;
95 static HANDLE receiver_thread_handle
;
97 static gboolean stop_receiver_thread
;
99 static gboolean needs_to_start
, started
;
101 #define agent_lock() EnterCriticalSection (&agent_mutex)
102 #define agent_unlock() LeaveCriticalSection (&agent_mutex)
103 static CRITICAL_SECTION agent_mutex
;
105 static void transport_connect (void);
107 static guint32 WINAPI
receiver_thread (void *arg
);
109 static void transport_start_receive (void);
112 * Functions to decode protocol data
115 decode_byte (guint8
*buf
, guint8
**endbuf
, guint8
*limit
)
118 g_assert (*endbuf
<= limit
);
123 decode_int (guint8
*buf
, guint8
**endbuf
, guint8
*limit
)
126 g_assert (*endbuf
<= limit
);
128 return (((int)buf
[0]) << 0) | (((int)buf
[1]) << 8) | (((int)buf
[2]) << 16) | (((int)buf
[3]) << 24);
132 decode_short (guint8
*buf
, guint8
**endbuf
, guint8
*limit
)
135 g_assert (*endbuf
<= limit
);
137 return (((int)buf
[0]) << 0) | (((int)buf
[1]) << 8);
141 decode_string_value (guint8
*buf
, guint8
**endbuf
, guint8
*limit
)
148 type
= decode_byte (p
, &p
, limit
);
149 if (type
== PRIM_TYPE_NULL
) {
153 g_assert (type
== PRIM_TYPE_STRING
);
157 guint8 b
= decode_byte (p
, &p
, limit
);
165 g_assert (length
< (1 << 16));
167 s
= g_malloc (length
+ 1);
169 g_assert (p
+ length
<= limit
);
170 memcpy (s
, p
, length
);
179 /********************************/
180 /* AGENT IMPLEMENTATION */
181 /********************************/
184 mono_attach_parse_options (char *options
)
188 if (!strcmp (options
, "disable"))
189 config
.enabled
= FALSE
;
193 mono_attach_init (void)
195 InitializeCriticalSection (&agent_mutex
);
197 config
.enabled
= TRUE
;
203 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
205 * Returns: whenever it was started.
208 mono_attach_start (void)
216 /* Check for the existence of the trigger file */
219 * We don't do anything with this file, and the only thing an attacker can do
220 * by creating it is to enable the attach mechanism if the process receives a
221 * SIGQUIT signal, which can only be sent by the owner/root.
223 snprintf (path
, sizeof (path
), "/tmp/.mono_attach_pid%d", getpid ());
224 fd
= open (path
, O_RDONLY
);
230 /* Act like we started */
237 * Our startup includes non signal-safe code, so ask the finalizer thread to
238 * do the actual startup.
240 needs_to_start
= TRUE
;
241 mono_gc_finalize_notify ();
246 /* Called by the finalizer thread when it is woken up */
248 mono_attach_maybe_start (void)
253 needs_to_start
= FALSE
;
255 transport_start_receive ();
262 mono_attach_cleanup (void)
267 unlink (ipc_filename
);
269 stop_receiver_thread
= TRUE
;
271 /* This will cause receiver_thread () to break out of the read () call */
274 /* Wait for the receiver thread to exit */
275 if (receiver_thread_handle
)
276 WaitForSingleObjectEx (receiver_thread_handle
, 0, FALSE
);
280 mono_attach_load_agent (MonoDomain
*domain
, char *agent
, char *args
, MonoObject
**exc
)
282 MonoAssembly
*agent_assembly
;
286 MonoArray
*main_args
;
288 MonoImageOpenStatus open_status
;
290 agent_assembly
= mono_assembly_open (agent
, &open_status
);
291 if (!agent_assembly
) {
292 fprintf (stderr
, "Cannot open agent assembly '%s': %s.\n", agent
, mono_image_strerror (open_status
));
298 * Can't use mono_jit_exec (), as it sets things which might confuse the
301 image
= mono_assembly_get_image (agent_assembly
);
302 entry
= mono_image_get_entry_point (image
);
304 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image
));
309 method
= mono_get_method (image
, entry
, NULL
);
311 g_print ("The entry point method of assembly '%s' could not be loaded\n", agent
);
317 main_args
= (MonoArray
*)mono_array_new (domain
, mono_defaults
.string_class
, 1);
318 mono_array_set (main_args
, MonoString
*, 0, mono_string_new (domain
, args
));
320 main_args
= (MonoArray
*)mono_array_new (domain
, mono_defaults
.string_class
, 0);
326 mono_runtime_invoke (method
, NULL
, pa
, exc
);
334 * Create a UNIX domain socket and bind it to a file in /tmp.
336 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
337 * permissions system to prevent attackers from connecting to the socket.
342 struct sockaddr_un name
;
345 char *filename
, *directory
;
351 if (getuid () != geteuid ()) {
352 fprintf (stderr
, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
356 /* Create the socket. */
357 sock
= socket (PF_UNIX
, SOCK_STREAM
, 0);
359 perror ("attach: failed to create IPC socket");
364 * For security reasons, create a directory to hold the listening socket,
365 * since there is a race between bind () and chmod () below.
367 /* FIXME: Use TMP ? */
369 #ifdef HAVE_GETPWUID_R
370 res
= getpwuid_r (getuid (), &pwbuf
, buf
, sizeof (buf
), &pw
);
372 pw
= getpwuid(getuid ());
373 res
= pw
!= NULL
? 0 : 1;
376 fprintf (stderr
, "attach: getpwuid_r () failed.\n");
380 directory
= g_strdup_printf ("/tmp/mono-%s", pw
->pw_name
);
381 res
= mkdir (directory
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
383 if (errno
== EEXIST
) {
384 /* Check type and permissions */
385 res
= lstat (directory
, &stat
);
387 perror ("attach: lstat () failed");
390 if (!S_ISDIR (stat
.st_mode
)) {
391 fprintf (stderr
, "attach: path '%s' is not a directory.\n", directory
);
394 if (stat
.st_uid
!= getuid ()) {
395 fprintf (stderr
, "attach: directory '%s' is not owned by the current user.\n", directory
);
398 if ((stat
.st_mode
& S_IRWXG
) != 0 || (stat
.st_mode
& S_IRWXO
) || ((stat
.st_mode
& S_IRWXU
) != (S_IRUSR
| S_IWUSR
| S_IXUSR
))) {
399 fprintf (stderr
, "attach: directory '%s' should have protection 0700.\n", directory
);
403 perror ("attach: mkdir () failed");
408 filename
= g_strdup_printf ("%s/.mono-%d", directory
, getpid ());
411 /* Bind a name to the socket. */
412 name
.sun_family
= AF_UNIX
;
413 strcpy (name
.sun_path
, filename
);
415 size
= (offsetof (struct sockaddr_un
, sun_path
)
416 + strlen (name
.sun_path
) + 1);
418 if (bind (sock
, (struct sockaddr
*) &name
, size
) < 0) {
419 fprintf (stderr
, "attach: failed to bind IPC socket '%s': %s\n", filename
, strerror (errno
));
424 /* Set permissions */
425 res
= chmod (filename
, S_IRUSR
| S_IWUSR
);
427 perror ("attach: failed to set permissions on IPC socket");
433 res
= listen (sock
, 16);
435 fprintf (stderr
, "attach: listen () failed: %s\n", strerror (errno
));
441 ipc_filename
= g_strdup (filename
);
443 server_uri
= g_strdup_printf ("unix://%s/.mono-%d?/vm", directory
, getpid ());
450 transport_connect (void)
458 transport_send (int fd
, guint8
*data
, int len
)
462 stats
.bytes_sent
+= len
;
463 //printf ("X: %d\n", stats.bytes_sent);
465 res
= write (fd
, data
, len
);
467 /* FIXME: What to do here ? */
474 transport_start_receive (void)
478 transport_connect ();
483 receiver_thread_handle
= mono_create_thread (NULL
, 0, receiver_thread
, NULL
, 0, &tid
);
484 g_assert (receiver_thread_handle
);
487 static guint32 WINAPI
488 receiver_thread (void *arg
)
490 int res
, content_len
;
495 printf ("attach: Listening on '%s'...\n", server_uri
);
498 conn_fd
= accept (listen_fd
, NULL
, NULL
);
500 /* Probably closed by mono_attach_cleanup () */
503 printf ("attach: Connected.\n");
505 mono_thread_attach (mono_get_root_domain ());
506 /* Ask the runtime to not abort this thread */
507 //mono_thread_current ()->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
508 /* Ask the runtime to not wait for this thread */
509 mono_thread_internal_current ()->state
|= ThreadState_Background
;
512 char *cmd
, *agent_name
, *agent_args
;
516 res
= read (conn_fd
, buffer
, 6);
518 if (res
== -1 && errno
== EINTR
)
521 if (res
== -1 || stop_receiver_thread
)
527 if ((strncmp ((char*)buffer
, "MONO", 4) != 0) || buffer
[4] != 1 || buffer
[5] != 0) {
528 fprintf (stderr
, "attach: message from server has unknown header.\n");
532 /* Read content length */
533 res
= read (conn_fd
, buffer
, 4);
540 content_len
= decode_int (p
, &p
, p_end
);
542 /* Read message body */
543 body
= g_malloc (content_len
);
544 res
= read (conn_fd
, body
, content_len
);
547 p_end
= body
+ content_len
;
549 cmd
= decode_string_value (p
, &p
, p_end
);
552 g_assert (!strcmp (cmd
, "attach"));
554 agent_name
= decode_string_value (p
, &p
, p_end
);
555 agent_args
= decode_string_value (p
, &p
, p_end
);
557 printf ("attach: Loading agent '%s'.\n", agent_name
);
558 mono_attach_load_agent (mono_domain_get (), agent_name
, agent_args
, &exc
);
562 // FIXME: Send back a result
568 printf ("attach: Disconnected.\n");
570 if (stop_receiver_thread
)
577 #else /* DISABLE_ATTACH */
580 mono_attach_parse_options (char *options
)
585 mono_attach_init (void)
590 mono_attach_start (void)
596 mono_attach_maybe_start (void)
601 mono_attach_cleanup (void)
605 #endif /* DISABLE_ATTACH */