2009-12-01 Jb Evain <jbevain@novell.com>
[mono.git] / mono / metadata / attach.c
blobb14709aeabc85b89e7e42f4e39c9b2c6a0d01a89
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 */
10 #include <config.h>
11 #include <glib.h>
13 #ifdef HOST_WIN32
14 #define DISABLE_ATTACH
15 #endif
16 #ifndef DISABLE_ATTACH
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <sys/stat.h>
24 #include <sys/un.h>
25 #include <netinet/in.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <pwd.h>
30 #include <errno.h>
31 #include <netdb.h>
32 #include <unistd.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>
41 #include "attach.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.
54 * SECURITY:
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.
68 typedef struct {
69 gboolean enabled;
70 } AgentConfig;
72 typedef struct {
73 int bytes_sent;
74 } AgentStats;
76 /*******************************************************************/
77 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
78 /*******************************************************************/
80 typedef enum {
81 PRIM_TYPE_INT32 = 8,
82 PRIM_TYPE_INT64 = 9,
83 PRIM_TYPE_NULL = 17,
84 PRIM_TYPE_STRING = 18
85 } PrimitiveType;
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
114 static inline int
115 decode_byte (guint8 *buf, guint8 **endbuf, guint8 *limit)
117 *endbuf = buf + 1;
118 g_assert (*endbuf <= limit);
119 return buf [0];
122 static inline int
123 decode_int (guint8 *buf, guint8 **endbuf, guint8 *limit)
125 *endbuf = buf + 4;
126 g_assert (*endbuf <= limit);
128 return (((int)buf [0]) << 0) | (((int)buf [1]) << 8) | (((int)buf [2]) << 16) | (((int)buf [3]) << 24);
131 static inline int
132 decode_short (guint8 *buf, guint8 **endbuf, guint8 *limit)
134 *endbuf = buf + 2;
135 g_assert (*endbuf <= limit);
137 return (((int)buf [0]) << 0) | (((int)buf [1]) << 8);
140 static char*
141 decode_string_value (guint8 *buf, guint8 **endbuf, guint8 *limit)
143 int type;
144 gint32 length;
145 guint8 *p = buf;
146 char *s;
148 type = decode_byte (p, &p, limit);
149 if (type == PRIM_TYPE_NULL) {
150 *endbuf = p;
151 return NULL;
153 g_assert (type == PRIM_TYPE_STRING);
155 length = 0;
156 while (TRUE) {
157 guint8 b = decode_byte (p, &p, limit);
159 length <<= 8;
160 length += b;
161 if (b <= 0x7f)
162 break;
165 g_assert (length < (1 << 16));
167 s = g_malloc (length + 1);
169 g_assert (p + length <= limit);
170 memcpy (s, p, length);
171 s [length] = '\0';
172 p += length;
174 *endbuf = p;
176 return s;
179 /********************************/
180 /* AGENT IMPLEMENTATION */
181 /********************************/
183 void
184 mono_attach_parse_options (char *options)
186 if (!options)
187 return;
188 if (!strcmp (options, "disable"))
189 config.enabled = FALSE;
192 void
193 mono_attach_init (void)
195 InitializeCriticalSection (&agent_mutex);
197 config.enabled = TRUE;
201 * mono_attach_start:
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.
207 gboolean
208 mono_attach_start (void)
210 char path [256];
211 int fd;
213 if (started)
214 return FALSE;
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);
225 if (fd == -1)
226 return FALSE;
227 close (fd);
229 if (!config.enabled)
230 /* Act like we started */
231 return TRUE;
233 if (started)
234 return FALSE;
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 ();
243 return TRUE;
246 /* Called by the finalizer thread when it is woken up */
247 void
248 mono_attach_maybe_start (void)
250 if (!needs_to_start)
251 return;
253 needs_to_start = FALSE;
254 if (!started) {
255 transport_start_receive ();
257 started = TRUE;
261 void
262 mono_attach_cleanup (void)
264 if (listen_fd)
265 close (listen_fd);
266 if (ipc_filename)
267 unlink (ipc_filename);
269 stop_receiver_thread = TRUE;
270 if (conn_fd)
271 /* This will cause receiver_thread () to break out of the read () call */
272 close (conn_fd);
274 /* Wait for the receiver thread to exit */
275 if (receiver_thread_handle)
276 WaitForSingleObjectEx (receiver_thread_handle, 0, FALSE);
279 static int
280 mono_attach_load_agent (MonoDomain *domain, char *agent, char *args, MonoObject **exc)
282 MonoAssembly *agent_assembly;
283 MonoImage *image;
284 MonoMethod *method;
285 guint32 entry;
286 MonoArray *main_args;
287 gpointer pa [1];
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));
293 g_free (agent);
294 return 2;
298 * Can't use mono_jit_exec (), as it sets things which might confuse the
299 * real Main method.
301 image = mono_assembly_get_image (agent_assembly);
302 entry = mono_image_get_entry_point (image);
303 if (!entry) {
304 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image));
305 g_free (agent);
306 return 1;
309 method = mono_get_method (image, entry, NULL);
310 if (method == NULL){
311 g_print ("The entry point method of assembly '%s' could not be loaded\n", agent);
312 g_free (agent);
313 return 1;
316 if (args) {
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));
319 } else {
320 main_args = (MonoArray*)mono_array_new (domain, mono_defaults.string_class, 0);
323 g_free (agent);
325 pa [0] = main_args;
326 mono_runtime_invoke (method, NULL, pa, exc);
328 return 0;
332 * ipc_connect:
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.
339 static void
340 ipc_connect (void)
342 struct sockaddr_un name;
343 int sock, res;
344 size_t size;
345 char *filename, *directory;
346 struct stat stat;
347 struct passwd pwbuf;
348 char buf [1024];
349 struct passwd *pw;
351 if (getuid () != geteuid ()) {
352 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
353 return;
356 /* Create the socket. */
357 sock = socket (PF_UNIX, SOCK_STREAM, 0);
358 if (sock < 0) {
359 perror ("attach: failed to create IPC socket");
360 return;
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 ? */
368 pw = NULL;
369 #ifdef HAVE_GETPWUID_R
370 res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
371 #else
372 pw = getpwuid(getuid ());
373 res = pw != NULL ? 0 : 1;
374 #endif
375 if (res != 0) {
376 fprintf (stderr, "attach: getpwuid_r () failed.\n");
377 return;
379 g_assert (pw);
380 directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
381 res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
382 if (res != 0) {
383 if (errno == EEXIST) {
384 /* Check type and permissions */
385 res = lstat (directory, &stat);
386 if (res != 0) {
387 perror ("attach: lstat () failed");
388 return;
390 if (!S_ISDIR (stat.st_mode)) {
391 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
392 return;
394 if (stat.st_uid != getuid ()) {
395 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
396 return;
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);
400 return;
402 } else {
403 perror ("attach: mkdir () failed");
404 return;
408 filename = g_strdup_printf ("%s/.mono-%d", directory, getpid ());
409 unlink (filename);
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));
420 close (sock);
421 return;
424 /* Set permissions */
425 res = chmod (filename, S_IRUSR | S_IWUSR);
426 if (res != 0) {
427 perror ("attach: failed to set permissions on IPC socket");
428 close (sock);
429 unlink (filename);
430 return;
433 res = listen (sock, 16);
434 if (res != 0) {
435 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
436 exit (1);
439 listen_fd = sock;
441 ipc_filename = g_strdup (filename);
443 server_uri = g_strdup_printf ("unix://%s/.mono-%d?/vm", directory, getpid ());
445 g_free (filename);
446 g_free (directory);
449 static void
450 transport_connect (void)
452 ipc_connect ();
455 #if 0
457 static void
458 transport_send (int fd, guint8 *data, int len)
460 int res;
462 stats.bytes_sent += len;
463 //printf ("X: %d\n", stats.bytes_sent);
465 res = write (fd, data, len);
466 if (res != len) {
467 /* FIXME: What to do here ? */
471 #endif
473 static void
474 transport_start_receive (void)
476 gsize tid;
478 transport_connect ();
480 if (!listen_fd)
481 return;
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;
491 guint8 buffer [256];
492 guint8 *p, *p_end;
493 MonoObject *exc;
495 printf ("attach: Listening on '%s'...\n", server_uri);
497 while (TRUE) {
498 conn_fd = accept (listen_fd, NULL, NULL);
499 if (conn_fd == -1)
500 /* Probably closed by mono_attach_cleanup () */
501 return 0;
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;
511 while (TRUE) {
512 char *cmd, *agent_name, *agent_args;
513 guint8 *body;
515 /* Read Header */
516 res = read (conn_fd, buffer, 6);
518 if (res == -1 && errno == EINTR)
519 continue;
521 if (res == -1 || stop_receiver_thread)
522 break;
524 if (res != 6)
525 break;
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");
529 break;
532 /* Read content length */
533 res = read (conn_fd, buffer, 4);
534 if (res != 4)
535 break;
537 p = buffer;
538 p_end = p + 8;
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);
546 p = body;
547 p_end = body + content_len;
549 cmd = decode_string_value (p, &p, p_end);
550 if (cmd == NULL)
551 break;
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);
560 g_free (body);
562 // FIXME: Send back a result
565 close (conn_fd);
566 conn_fd = 0;
568 printf ("attach: Disconnected.\n");
570 if (stop_receiver_thread)
571 break;
574 return 0;
577 #else /* DISABLE_ATTACH */
579 void
580 mono_attach_parse_options (char *options)
584 void
585 mono_attach_init (void)
589 gboolean
590 mono_attach_start (void)
592 return FALSE;
595 void
596 mono_attach_maybe_start (void)
600 void
601 mono_attach_cleanup (void)
605 #endif /* DISABLE_ATTACH */