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)
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
15 #define DISABLE_ATTACH
17 #ifndef DISABLE_ATTACH
22 #include <sys/types.h>
23 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <sys/types.h>
36 #include <mono/metadata/assembly.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>
45 #include <mono/io-layer/io-layer.h>
48 * This module enables other processes to attach to a running mono process and
49 * load agent assemblies.
50 * Communication is done through a UNIX Domain Socket located at
51 * /tmp/mono-<USER>/.mono-<PID>.
52 * We use a simplified version of the .net remoting protocol.
53 * To increase security, and to avoid spinning up a listener thread on startup,
54 * we follow the java implementation, and only start up the attach mechanism
55 * when we receive a QUIT signal and there is a file named
56 * '.mono_attach_pid<PID>' in /tmp.
59 * - This module allows loading of arbitrary code into a running mono runtime, so
60 * it is security critical.
61 * - Security is based on controlling access to the unix file to which the unix
62 * domain socket is bound. Permissions/ownership are set such that only the owner
63 * of the process can access the socket.
64 * - As an additional measure, the socket is only created when the process receives
65 * a SIGQUIT signal, which only its owner/root can send.
66 * - The socket is kept in a directory whose ownership is checked before creating
67 * the socket. This could allow an attacker a kind of DOS attack by creating the
68 * directory with the wrong permissions/ownership. However, the only thing such
69 * an attacker could prevent is the attaching of agents to the mono runtime.
80 /*******************************************************************/
81 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
82 /*******************************************************************/
91 static AgentConfig config;
93 static int listen_fd, conn_fd;
95 static char *ipc_filename;
97 static char *server_uri;
99 static MonoThreadHandle *receiver_thread_handle;
101 static gboolean stop_receiver_thread;
103 static gboolean needs_to_start, started;
105 static void transport_connect (void);
107 static gsize 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_string_value (guint8 *buf, guint8 **endbuf, guint8 *limit)
139 type = decode_byte (p, &p, limit);
140 if (type == PRIM_TYPE_NULL) {
144 g_assert (type == PRIM_TYPE_STRING);
148 guint8 b = decode_byte (p, &p, limit);
156 g_assert (length < (1 << 16));
158 s = (char *)g_malloc (length + 1);
160 g_assert (p + length <= limit);
161 memcpy (s, p, length);
170 /********************************/
171 /* AGENT IMPLEMENTATION */
172 /********************************/
175 mono_attach_parse_options (char *options)
179 if (!strcmp (options, "disable"))
180 config.enabled = FALSE;
184 mono_attach_init (void)
186 config.enabled = TRUE;
192 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
194 * Returns: whenever it was started.
197 mono_attach_start (void)
205 /* Check for the existence of the trigger file */
208 * We don't do anything with this file, and the only thing an attacker can do
209 * by creating it is to enable the attach mechanism if the process receives a
210 * SIGQUIT signal, which can only be sent by the owner/root.
212 snprintf (path, sizeof (path), "/tmp/.mono_attach_pid%"PRIdMAX"", (intmax_t) getpid ());
213 fd = open (path, O_RDONLY);
219 /* Act like we started */
226 * Our startup includes non signal-safe code, so ask the finalizer thread to
227 * do the actual startup.
229 needs_to_start = TRUE;
230 mono_gc_finalize_notify ();
235 /* Called by the finalizer thread when it is woken up */
237 mono_attach_maybe_start (void)
242 needs_to_start = FALSE;
244 transport_start_receive ();
251 mono_attach_cleanup (void)
256 unlink (ipc_filename);
258 stop_receiver_thread = TRUE;
260 /* This will cause receiver_thread () to break out of the read () call */
263 /* Wait for the receiver thread to exit */
264 if (receiver_thread_handle)
265 mono_thread_info_wait_one_handle (receiver_thread_handle, 0, FALSE);
269 mono_attach_load_agent (MonoDomain *domain, char *agent, char *args, MonoObject **exc)
272 MonoAssembly *agent_assembly;
276 MonoArray *main_args;
278 MonoImageOpenStatus open_status;
280 agent_assembly = mono_assembly_open (agent, &open_status);
281 if (!agent_assembly) {
282 fprintf (stderr, "Cannot open agent assembly '%s': %s.\n", agent, mono_image_strerror (open_status));
288 * Can't use mono_jit_exec (), as it sets things which might confuse the
291 image = mono_assembly_get_image (agent_assembly);
292 entry = mono_image_get_entry_point (image);
294 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image));
299 method = mono_get_method_checked (image, entry, NULL, NULL, &error);
301 g_print ("The entry point method of assembly '%s' could not be loaded due to %s\n", agent, mono_error_get_message (&error));
302 mono_error_cleanup (&error);
308 main_args = (MonoArray*)mono_array_new_checked (domain, mono_defaults.string_class, (args == NULL) ? 0 : 1, &error);
309 if (main_args == NULL) {
310 g_print ("Could not allocate main method args due to %s\n", mono_error_get_message (&error));
311 mono_error_cleanup (&error);
317 mono_array_set (main_args, MonoString*, 0, mono_string_new (domain, args));
322 mono_runtime_try_invoke (method, NULL, pa, exc, &error);
323 if (!is_ok (&error)) {
324 g_print ("The entry point method of assembly '%s' could not be executed due to %s\n", agent, mono_error_get_message (&error));
325 mono_error_cleanup (&error);
338 * Create a UNIX domain socket and bind it to a file in /tmp.
340 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
341 * permissions system to prevent attackers from connecting to the socket.
346 struct sockaddr_un name;
349 char *filename, *directory;
355 if (getuid () != geteuid ()) {
356 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
360 /* Create the socket. */
361 sock = socket (PF_UNIX, SOCK_STREAM, 0);
363 perror ("attach: failed to create IPC socket");
368 * For security reasons, create a directory to hold the listening socket,
369 * since there is a race between bind () and chmod () below.
371 /* FIXME: Use TMP ? */
373 #ifdef HAVE_GETPWUID_R
374 res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
376 pw = getpwuid(getuid ());
377 res = pw != NULL ? 0 : 1;
380 fprintf (stderr, "attach: getpwuid_r () failed.\n");
384 directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
385 res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
387 if (errno == EEXIST) {
388 /* Check type and permissions */
389 res = lstat (directory, &stat);
391 perror ("attach: lstat () failed");
394 if (!S_ISDIR (stat.st_mode)) {
395 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
398 if (stat.st_uid != getuid ()) {
399 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
402 if ((stat.st_mode & S_IRWXG) != 0 || (stat.st_mode & S_IRWXO) || ((stat.st_mode & S_IRWXU) != (S_IRUSR | S_IWUSR | S_IXUSR))) {
403 fprintf (stderr, "attach: directory '%s' should have protection 0700.\n", directory);
407 perror ("attach: mkdir () failed");
412 filename = g_strdup_printf ("%s/.mono-%"PRIdMAX"", directory, (intmax_t) getpid ());
415 /* Bind a name to the socket. */
416 name.sun_family = AF_UNIX;
417 strcpy (name.sun_path, filename);
419 size = (offsetof (struct sockaddr_un, sun_path)
420 + strlen (name.sun_path) + 1);
422 if (bind (sock, (struct sockaddr *) &name, size) < 0) {
423 fprintf (stderr, "attach: failed to bind IPC socket '%s': %s\n", filename, strerror (errno));
428 /* Set permissions */
429 res = chmod (filename, S_IRUSR | S_IWUSR);
431 perror ("attach: failed to set permissions on IPC socket");
437 res = listen (sock, 16);
439 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
445 ipc_filename = g_strdup (filename);
447 server_uri = g_strdup_printf ("unix://%s/.mono-%"PRIdMAX"?/vm", directory, (intmax_t) getpid ());
454 transport_connect (void)
462 transport_send (int fd, guint8 *data, int len)
466 stats.bytes_sent += len;
467 //printf ("X: %d\n", stats.bytes_sent);
469 res = write (fd, data, len);
471 /* FIXME: What to do here ? */
478 transport_start_receive (void)
480 transport_connect ();
485 receiver_thread_handle = mono_threads_create_thread (receiver_thread, NULL, NULL, NULL);
486 g_assert (receiver_thread_handle);
490 receiver_thread (void *arg)
493 int res, content_len;
498 mono_native_thread_set_name (mono_native_thread_id_get (), "Attach receiver");
500 printf ("attach: Listening on '%s'...\n", server_uri);
503 conn_fd = accept (listen_fd, NULL, NULL);
505 /* Probably closed by mono_attach_cleanup () */
508 printf ("attach: Connected.\n");
510 MonoThread *thread = mono_thread_attach (mono_get_root_domain ());
511 mono_thread_set_name_internal (thread->internal_thread, mono_string_new (mono_get_root_domain (), "Attach receiver"), TRUE, &error);
512 mono_error_assert_ok (&error);
513 /* Ask the runtime to not abort this thread */
514 //mono_thread_current ()->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
515 /* Ask the runtime to not wait for this thread */
516 thread->internal_thread->state |= ThreadState_Background;
519 char *cmd, *agent_name, *agent_args;
523 res = read (conn_fd, buffer, 6);
525 if (res == -1 && errno == EINTR)
528 if (res == -1 || stop_receiver_thread)
534 if ((strncmp ((char*)buffer, "MONO", 4) != 0) || buffer [4] != 1 || buffer [5] != 0) {
535 fprintf (stderr, "attach: message from server has unknown header.\n");
539 /* Read content length */
540 res = read (conn_fd, buffer, 4);
547 content_len = decode_int (p, &p, p_end);
549 /* Read message body */
550 body = (guint8 *)g_malloc (content_len);
551 res = read (conn_fd, body, content_len);
554 p_end = body + content_len;
556 cmd = decode_string_value (p, &p, p_end);
559 g_assert (!strcmp (cmd, "attach"));
561 agent_name = decode_string_value (p, &p, p_end);
562 agent_args = decode_string_value (p, &p, p_end);
564 printf ("attach: Loading agent '%s'.\n", agent_name);
565 mono_attach_load_agent (mono_domain_get (), agent_name, agent_args, &exc);
569 // FIXME: Send back a result
575 printf ("attach: Disconnected.\n");
577 if (stop_receiver_thread)
584 #else /* DISABLE_ATTACH */
587 mono_attach_parse_options (char *options)
592 mono_attach_init (void)
597 mono_attach_start (void)
603 mono_attach_maybe_start (void)
608 mono_attach_cleanup (void)
612 #endif /* DISABLE_ATTACH */