[linker] We need to mark nested types even if the declaring type isn't marked.
[mono-project.git] / mono / metadata / attach.c
blobea7fa289a3899b91b7917f21311f394b2726788f
1 /*
2 * attach.c: Support for attaching to the runtime from other processes.
4 * Author:
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.
9 */
11 #include <config.h>
12 #include <glib.h>
14 #ifdef HOST_WIN32
15 #define DISABLE_ATTACH
16 #endif
17 #ifndef DISABLE_ATTACH
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/un.h>
26 #include <netinet/in.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <inttypes.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <netdb.h>
34 #include <unistd.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>
43 #include "attach.h"
46 * This module enables other processes to attach to a running mono process and
47 * load agent assemblies.
48 * Communication is done through a UNIX Domain Socket located at
49 * /tmp/mono-<USER>/.mono-<PID>.
50 * We use a simplified version of the .net remoting protocol.
51 * To increase security, and to avoid spinning up a listener thread on startup,
52 * we follow the java implementation, and only start up the attach mechanism
53 * when we receive a QUIT signal and there is a file named
54 * '.mono_attach_pid<PID>' in /tmp.
56 * SECURITY:
57 * - This module allows loading of arbitrary code into a running mono runtime, so
58 * it is security critical.
59 * - Security is based on controlling access to the unix file to which the unix
60 * domain socket is bound. Permissions/ownership are set such that only the owner
61 * of the process can access the socket.
62 * - As an additional measure, the socket is only created when the process receives
63 * a SIGQUIT signal, which only its owner/root can send.
64 * - The socket is kept in a directory whose ownership is checked before creating
65 * the socket. This could allow an attacker a kind of DOS attack by creating the
66 * directory with the wrong permissions/ownership. However, the only thing such
67 * an attacker could prevent is the attaching of agents to the mono runtime.
70 typedef struct {
71 gboolean enabled;
72 } AgentConfig;
74 typedef struct {
75 int bytes_sent;
76 } AgentStats;
78 /*******************************************************************/
79 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
80 /*******************************************************************/
82 typedef enum {
83 PRIM_TYPE_INT32 = 8,
84 PRIM_TYPE_INT64 = 9,
85 PRIM_TYPE_NULL = 17,
86 PRIM_TYPE_STRING = 18
87 } PrimitiveType;
89 static AgentConfig config;
91 static int listen_fd, conn_fd;
93 static char *ipc_filename;
95 static char *server_uri;
97 static HANDLE receiver_thread_handle;
99 static gboolean stop_receiver_thread;
101 static gboolean needs_to_start, started;
103 static void transport_connect (void);
105 static gsize WINAPI receiver_thread (void *arg);
107 static void transport_start_receive (void);
110 * Functions to decode protocol data
112 static inline int
113 decode_byte (guint8 *buf, guint8 **endbuf, guint8 *limit)
115 *endbuf = buf + 1;
116 g_assert (*endbuf <= limit);
117 return buf [0];
120 static inline int
121 decode_int (guint8 *buf, guint8 **endbuf, guint8 *limit)
123 *endbuf = buf + 4;
124 g_assert (*endbuf <= limit);
126 return (((int)buf [0]) << 0) | (((int)buf [1]) << 8) | (((int)buf [2]) << 16) | (((int)buf [3]) << 24);
129 static char*
130 decode_string_value (guint8 *buf, guint8 **endbuf, guint8 *limit)
132 int type;
133 gint32 length;
134 guint8 *p = buf;
135 char *s;
137 type = decode_byte (p, &p, limit);
138 if (type == PRIM_TYPE_NULL) {
139 *endbuf = p;
140 return NULL;
142 g_assert (type == PRIM_TYPE_STRING);
144 length = 0;
145 while (TRUE) {
146 guint8 b = decode_byte (p, &p, limit);
148 length <<= 8;
149 length += b;
150 if (b <= 0x7f)
151 break;
154 g_assert (length < (1 << 16));
156 s = (char *)g_malloc (length + 1);
158 g_assert (p + length <= limit);
159 memcpy (s, p, length);
160 s [length] = '\0';
161 p += length;
163 *endbuf = p;
165 return s;
168 /********************************/
169 /* AGENT IMPLEMENTATION */
170 /********************************/
172 void
173 mono_attach_parse_options (char *options)
175 if (!options)
176 return;
177 if (!strcmp (options, "disable"))
178 config.enabled = FALSE;
181 void
182 mono_attach_init (void)
184 config.enabled = TRUE;
188 * mono_attach_start:
190 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
192 * Returns: whenever it was started.
194 gboolean
195 mono_attach_start (void)
197 char path [256];
198 int fd;
200 if (started)
201 return FALSE;
203 /* Check for the existence of the trigger file */
206 * We don't do anything with this file, and the only thing an attacker can do
207 * by creating it is to enable the attach mechanism if the process receives a
208 * SIGQUIT signal, which can only be sent by the owner/root.
210 snprintf (path, sizeof (path), "/tmp/.mono_attach_pid%"PRIdMAX"", (intmax_t) getpid ());
211 fd = open (path, O_RDONLY);
212 if (fd == -1)
213 return FALSE;
214 close (fd);
216 if (!config.enabled)
217 /* Act like we started */
218 return TRUE;
220 if (started)
221 return FALSE;
224 * Our startup includes non signal-safe code, so ask the finalizer thread to
225 * do the actual startup.
227 needs_to_start = TRUE;
228 mono_gc_finalize_notify ();
230 return TRUE;
233 /* Called by the finalizer thread when it is woken up */
234 void
235 mono_attach_maybe_start (void)
237 if (!needs_to_start)
238 return;
240 needs_to_start = FALSE;
241 if (!started) {
242 transport_start_receive ();
244 started = TRUE;
248 void
249 mono_attach_cleanup (void)
251 if (listen_fd)
252 close (listen_fd);
253 if (ipc_filename)
254 unlink (ipc_filename);
256 stop_receiver_thread = TRUE;
257 if (conn_fd)
258 /* This will cause receiver_thread () to break out of the read () call */
259 close (conn_fd);
261 /* Wait for the receiver thread to exit */
262 if (receiver_thread_handle)
263 WaitForSingleObjectEx (receiver_thread_handle, 0, FALSE);
266 static int
267 mono_attach_load_agent (MonoDomain *domain, char *agent, char *args, MonoObject **exc)
269 MonoError error;
270 MonoAssembly *agent_assembly;
271 MonoImage *image;
272 MonoMethod *method;
273 guint32 entry;
274 MonoArray *main_args;
275 gpointer pa [1];
276 MonoImageOpenStatus open_status;
278 agent_assembly = mono_assembly_open (agent, &open_status);
279 if (!agent_assembly) {
280 fprintf (stderr, "Cannot open agent assembly '%s': %s.\n", agent, mono_image_strerror (open_status));
281 g_free (agent);
282 return 2;
286 * Can't use mono_jit_exec (), as it sets things which might confuse the
287 * real Main method.
289 image = mono_assembly_get_image (agent_assembly);
290 entry = mono_image_get_entry_point (image);
291 if (!entry) {
292 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image));
293 g_free (agent);
294 return 1;
297 method = mono_get_method_checked (image, entry, NULL, NULL, &error);
298 if (method == NULL){
299 g_print ("The entry point method of assembly '%s' could not be loaded due to %s\n", agent, mono_error_get_message (&error));
300 mono_error_cleanup (&error);
301 g_free (agent);
302 return 1;
306 main_args = (MonoArray*)mono_array_new_checked (domain, mono_defaults.string_class, (args == NULL) ? 0 : 1, &error);
307 if (main_args == NULL) {
308 g_print ("Could not allocate main method args due to %s\n", mono_error_get_message (&error));
309 mono_error_cleanup (&error);
310 g_free (agent);
311 return 1;
314 if (args) {
315 mono_array_set (main_args, MonoString*, 0, mono_string_new (domain, args));
319 pa [0] = main_args;
320 mono_runtime_try_invoke (method, NULL, pa, exc, &error);
321 if (!is_ok (&error)) {
322 g_print ("The entry point method of assembly '%s' could not be executed due to %s\n", agent, mono_error_get_message (&error));
323 mono_error_cleanup (&error);
324 g_free (agent);
325 return 1;
328 g_free (agent);
330 return 0;
334 * ipc_connect:
336 * Create a UNIX domain socket and bind it to a file in /tmp.
338 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
339 * permissions system to prevent attackers from connecting to the socket.
341 static void
342 ipc_connect (void)
344 struct sockaddr_un name;
345 int sock, res;
346 size_t size;
347 char *filename, *directory;
348 struct stat stat;
349 struct passwd pwbuf;
350 char buf [1024];
351 struct passwd *pw;
353 if (getuid () != geteuid ()) {
354 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
355 return;
358 /* Create the socket. */
359 sock = socket (PF_UNIX, SOCK_STREAM, 0);
360 if (sock < 0) {
361 perror ("attach: failed to create IPC socket");
362 return;
366 * For security reasons, create a directory to hold the listening socket,
367 * since there is a race between bind () and chmod () below.
369 /* FIXME: Use TMP ? */
370 pw = NULL;
371 #ifdef HAVE_GETPWUID_R
372 res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
373 #else
374 pw = getpwuid(getuid ());
375 res = pw != NULL ? 0 : 1;
376 #endif
377 if (res != 0) {
378 fprintf (stderr, "attach: getpwuid_r () failed.\n");
379 return;
381 g_assert (pw);
382 directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
383 res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
384 if (res != 0) {
385 if (errno == EEXIST) {
386 /* Check type and permissions */
387 res = lstat (directory, &stat);
388 if (res != 0) {
389 perror ("attach: lstat () failed");
390 return;
392 if (!S_ISDIR (stat.st_mode)) {
393 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
394 return;
396 if (stat.st_uid != getuid ()) {
397 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
398 return;
400 if ((stat.st_mode & S_IRWXG) != 0 || (stat.st_mode & S_IRWXO) || ((stat.st_mode & S_IRWXU) != (S_IRUSR | S_IWUSR | S_IXUSR))) {
401 fprintf (stderr, "attach: directory '%s' should have protection 0700.\n", directory);
402 return;
404 } else {
405 perror ("attach: mkdir () failed");
406 return;
410 filename = g_strdup_printf ("%s/.mono-%"PRIdMAX"", directory, (intmax_t) getpid ());
411 unlink (filename);
413 /* Bind a name to the socket. */
414 name.sun_family = AF_UNIX;
415 strcpy (name.sun_path, filename);
417 size = (offsetof (struct sockaddr_un, sun_path)
418 + strlen (name.sun_path) + 1);
420 if (bind (sock, (struct sockaddr *) &name, size) < 0) {
421 fprintf (stderr, "attach: failed to bind IPC socket '%s': %s\n", filename, strerror (errno));
422 close (sock);
423 return;
426 /* Set permissions */
427 res = chmod (filename, S_IRUSR | S_IWUSR);
428 if (res != 0) {
429 perror ("attach: failed to set permissions on IPC socket");
430 close (sock);
431 unlink (filename);
432 return;
435 res = listen (sock, 16);
436 if (res != 0) {
437 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
438 exit (1);
441 listen_fd = sock;
443 ipc_filename = g_strdup (filename);
445 server_uri = g_strdup_printf ("unix://%s/.mono-%"PRIdMAX"?/vm", directory, (intmax_t) getpid ());
447 g_free (filename);
448 g_free (directory);
451 static void
452 transport_connect (void)
454 ipc_connect ();
457 #if 0
459 static void
460 transport_send (int fd, guint8 *data, int len)
462 int res;
464 stats.bytes_sent += len;
465 //printf ("X: %d\n", stats.bytes_sent);
467 res = write (fd, data, len);
468 if (res != len) {
469 /* FIXME: What to do here ? */
473 #endif
475 static void
476 transport_start_receive (void)
478 transport_connect ();
480 if (!listen_fd)
481 return;
483 receiver_thread_handle = mono_threads_create_thread (receiver_thread, NULL, 0, NULL);
484 g_assert (receiver_thread_handle);
487 static gsize WINAPI
488 receiver_thread (void *arg)
490 MonoError error;
491 int res, content_len;
492 guint8 buffer [256];
493 guint8 *p, *p_end;
494 MonoObject *exc;
496 mono_native_thread_set_name (mono_native_thread_id_get (), "Attach receiver");
498 printf ("attach: Listening on '%s'...\n", server_uri);
500 while (TRUE) {
501 conn_fd = accept (listen_fd, NULL, NULL);
502 if (conn_fd == -1)
503 /* Probably closed by mono_attach_cleanup () */
504 return 0;
506 printf ("attach: Connected.\n");
508 MonoThread *thread = mono_thread_attach (mono_get_root_domain ());
509 mono_thread_set_name_internal (thread->internal_thread, mono_string_new (mono_get_root_domain (), "Attach receiver"), TRUE, &error);
510 mono_error_assert_ok (&error);
511 /* Ask the runtime to not abort this thread */
512 //mono_thread_current ()->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
513 /* Ask the runtime to not wait for this thread */
514 thread->internal_thread->state |= ThreadState_Background;
516 while (TRUE) {
517 char *cmd, *agent_name, *agent_args;
518 guint8 *body;
520 /* Read Header */
521 res = read (conn_fd, buffer, 6);
523 if (res == -1 && errno == EINTR)
524 continue;
526 if (res == -1 || stop_receiver_thread)
527 break;
529 if (res != 6)
530 break;
532 if ((strncmp ((char*)buffer, "MONO", 4) != 0) || buffer [4] != 1 || buffer [5] != 0) {
533 fprintf (stderr, "attach: message from server has unknown header.\n");
534 break;
537 /* Read content length */
538 res = read (conn_fd, buffer, 4);
539 if (res != 4)
540 break;
542 p = buffer;
543 p_end = p + 8;
545 content_len = decode_int (p, &p, p_end);
547 /* Read message body */
548 body = (guint8 *)g_malloc (content_len);
549 res = read (conn_fd, body, content_len);
551 p = body;
552 p_end = body + content_len;
554 cmd = decode_string_value (p, &p, p_end);
555 if (cmd == NULL)
556 break;
557 g_assert (!strcmp (cmd, "attach"));
559 agent_name = decode_string_value (p, &p, p_end);
560 agent_args = decode_string_value (p, &p, p_end);
562 printf ("attach: Loading agent '%s'.\n", agent_name);
563 mono_attach_load_agent (mono_domain_get (), agent_name, agent_args, &exc);
565 g_free (body);
567 // FIXME: Send back a result
570 close (conn_fd);
571 conn_fd = 0;
573 printf ("attach: Disconnected.\n");
575 if (stop_receiver_thread)
576 break;
579 return 0;
582 #else /* DISABLE_ATTACH */
584 void
585 mono_attach_parse_options (char *options)
589 void
590 mono_attach_init (void)
594 gboolean
595 mono_attach_start (void)
597 return FALSE;
600 void
601 mono_attach_maybe_start (void)
605 void
606 mono_attach_cleanup (void)
610 #endif /* DISABLE_ATTACH */