[io-layer] Extract error (#4279)
[mono.git] / mono / metadata / attach.c
1 /*
2  * attach.c: Support for attaching to the runtime from other processes.
3  *
4  * Author:
5  *   Zoltan Varga (vargaz@gmail.com)
6  *
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  */
10
11 #include <config.h>
12 #include <glib.h>
13
14 #ifdef HOST_WIN32
15 #define DISABLE_ATTACH
16 #endif
17 #ifndef DISABLE_ATTACH
18
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>
35
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"
44
45 #include <mono/utils/w32api.h>
46
47 /*
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.
57  *
58  * SECURITY:
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.
70  */
71
72 typedef struct {
73         gboolean enabled;
74 } AgentConfig;
75
76 typedef struct {
77         int bytes_sent;
78 } AgentStats;
79
80 /*******************************************************************/
81 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
82 /*******************************************************************/
83
84 typedef enum {
85         PRIM_TYPE_INT32 = 8,
86         PRIM_TYPE_INT64 = 9,
87         PRIM_TYPE_NULL = 17,
88         PRIM_TYPE_STRING = 18
89 } PrimitiveType;
90
91 static AgentConfig config;
92
93 static int listen_fd, conn_fd;
94
95 static char *ipc_filename;
96
97 static char *server_uri;
98
99 static MonoThreadHandle *receiver_thread_handle;
100
101 static gboolean stop_receiver_thread;
102
103 static gboolean needs_to_start, started;
104
105 static void transport_connect (void);
106
107 static gsize WINAPI receiver_thread (void *arg);
108
109 static void transport_start_receive (void);
110
111 /*
112  * Functions to decode protocol data
113  */
114 static inline int
115 decode_byte (guint8 *buf, guint8 **endbuf, guint8 *limit)
116 {
117         *endbuf = buf + 1;
118         g_assert (*endbuf <= limit);
119         return buf [0];
120 }
121
122 static inline int
123 decode_int (guint8 *buf, guint8 **endbuf, guint8 *limit)
124 {
125         *endbuf = buf + 4;
126         g_assert (*endbuf <= limit);
127
128         return (((int)buf [0]) << 0) | (((int)buf [1]) << 8) | (((int)buf [2]) << 16) | (((int)buf [3]) << 24);
129 }
130
131 static char*
132 decode_string_value (guint8 *buf, guint8 **endbuf, guint8 *limit)
133 {
134         int type;
135     gint32 length;
136         guint8 *p = buf;
137         char *s;
138
139         type = decode_byte (p, &p, limit);
140         if (type == PRIM_TYPE_NULL) {
141                 *endbuf = p;
142                 return NULL;
143         }
144         g_assert (type == PRIM_TYPE_STRING);
145
146         length = 0;
147         while (TRUE) {
148                 guint8 b = decode_byte (p, &p, limit);
149                 
150                 length <<= 8;
151                 length += b;
152                 if (b <= 0x7f)
153                         break;
154         }
155
156         g_assert (length < (1 << 16));
157
158         s = (char *)g_malloc (length + 1);
159
160         g_assert (p + length <= limit);
161         memcpy (s, p, length);
162         s [length] = '\0';
163         p += length;
164
165         *endbuf = p;
166
167         return s;
168 }
169
170 /********************************/
171 /*    AGENT IMPLEMENTATION      */
172 /********************************/
173
174 void
175 mono_attach_parse_options (char *options)
176 {
177         if (!options)
178                 return;
179         if (!strcmp (options, "disable"))
180                 config.enabled = FALSE;
181 }
182
183 void
184 mono_attach_init (void)
185 {
186         config.enabled = TRUE;
187 }
188
189 /**
190  * mono_attach_start:
191  *
192  * Start the attach mechanism if needed.  This is called from a signal handler so it must be signal safe.
193  *
194  * Returns: whenever it was started.
195  */
196 gboolean
197 mono_attach_start (void)
198 {
199         char path [256];
200         int fd;
201
202         if (started)
203                 return FALSE;
204
205         /* Check for the existence of the trigger file */
206
207         /* 
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.
211          */
212         snprintf (path, sizeof (path), "/tmp/.mono_attach_pid%"PRIdMAX"", (intmax_t) getpid ());
213         fd = open (path, O_RDONLY);
214         if (fd == -1)
215                 return FALSE;
216         close (fd);
217
218         if (!config.enabled)
219                 /* Act like we started */
220                 return TRUE;
221
222         if (started)
223                 return FALSE;
224
225         /*
226          * Our startup includes non signal-safe code, so ask the finalizer thread to 
227          * do the actual startup.
228          */
229         needs_to_start = TRUE;
230         mono_gc_finalize_notify ();
231
232         return TRUE;
233 }
234
235 /* Called by the finalizer thread when it is woken up */
236 void
237 mono_attach_maybe_start (void)
238 {
239         if (!needs_to_start)
240                 return;
241
242         needs_to_start = FALSE;
243         if (!started) {
244                 transport_start_receive ();
245
246                 started = TRUE;
247         }
248 }
249
250 void
251 mono_attach_cleanup (void)
252 {
253         if (listen_fd)
254                 close (listen_fd);
255         if (ipc_filename)
256                 unlink (ipc_filename);
257
258         stop_receiver_thread = TRUE;
259         if (conn_fd)
260                 /* This will cause receiver_thread () to break out of the read () call */
261                 close (conn_fd);
262
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);
266 }
267
268 static int
269 mono_attach_load_agent (MonoDomain *domain, char *agent, char *args, MonoObject **exc)
270 {
271         MonoError error;
272         MonoAssembly *agent_assembly;
273         MonoImage *image;
274         MonoMethod *method;
275         guint32 entry;
276         MonoArray *main_args;
277         gpointer pa [1];
278         MonoImageOpenStatus open_status;
279
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));
283                 g_free (agent);
284                 return 2;
285         }
286
287         /* 
288          * Can't use mono_jit_exec (), as it sets things which might confuse the
289          * real Main method.
290          */
291         image = mono_assembly_get_image (agent_assembly);
292         entry = mono_image_get_entry_point (image);
293         if (!entry) {
294                 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image));
295                 g_free (agent);
296                 return 1;
297         }
298
299         method = mono_get_method_checked (image, entry, NULL, NULL, &error);
300         if (method == NULL){
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);
303                 g_free (agent);
304                 return 1;
305         }
306         
307         
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);
312                 g_free (agent);
313                 return 1;
314         }
315
316         if (args) {
317                 mono_array_set (main_args, MonoString*, 0, mono_string_new (domain, args));
318         }
319
320
321         pa [0] = main_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);
326                 g_free (agent);
327                 return 1;
328         }
329
330         g_free (agent);
331
332         return 0;
333 }
334
335 /*
336  * ipc_connect:
337  *
338  *   Create a UNIX domain socket and bind it to a file in /tmp.
339  *
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.
342  */
343 static void
344 ipc_connect (void)
345 {
346         struct sockaddr_un name;
347         int sock, res;
348         size_t size;
349         char *filename, *directory;
350         struct stat stat;
351         struct passwd pwbuf;
352         char buf [1024];
353         struct passwd *pw;
354
355         if (getuid () != geteuid ()) {
356                 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
357                 return;
358         }
359
360         /* Create the socket.   */  
361         sock = socket (PF_UNIX, SOCK_STREAM, 0);
362         if (sock < 0) {
363                 perror ("attach: failed to create IPC socket");
364                 return;
365         }
366
367         /* 
368          * For security reasons, create a directory to hold the listening socket,
369          * since there is a race between bind () and chmod () below.
370          */
371         /* FIXME: Use TMP ? */
372         pw = NULL;
373 #ifdef HAVE_GETPWUID_R
374         res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
375 #else
376         pw = getpwuid(getuid ());
377         res = pw != NULL ? 0 : 1;
378 #endif
379         if (res != 0) {
380                 fprintf (stderr, "attach: getpwuid_r () failed.\n");
381                 return;
382         }
383         g_assert (pw);
384         directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
385         res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
386         if (res != 0) {
387                 if (errno == EEXIST) {
388                         /* Check type and permissions */
389                         res = lstat (directory, &stat);
390                         if (res != 0) {
391                                 perror ("attach: lstat () failed");
392                                 return;
393                         }
394                         if (!S_ISDIR (stat.st_mode)) {
395                                 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
396                                 return;
397                         }
398                         if (stat.st_uid != getuid ()) {
399                                 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
400                                 return;
401                         }
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);
404                                 return;
405                         }
406                 } else {
407                         perror ("attach: mkdir () failed");
408                         return;
409                 }
410         }
411
412         filename = g_strdup_printf ("%s/.mono-%"PRIdMAX"", directory, (intmax_t) getpid ());
413         unlink (filename);
414
415         /* Bind a name to the socket.   */
416         name.sun_family = AF_UNIX;
417         strcpy (name.sun_path, filename);
418
419         size = (offsetof (struct sockaddr_un, sun_path)
420                         + strlen (name.sun_path) + 1);
421
422         if (bind (sock, (struct sockaddr *) &name, size) < 0) {
423                 fprintf (stderr, "attach: failed to bind IPC socket '%s': %s\n", filename, strerror (errno));
424                 close (sock);
425                 return;
426         }
427
428         /* Set permissions */
429         res = chmod (filename, S_IRUSR | S_IWUSR);
430         if (res != 0) {
431                 perror ("attach: failed to set permissions on IPC socket");
432                 close (sock);
433                 unlink (filename);
434                 return;
435         }
436
437         res = listen (sock, 16);
438         if (res != 0) {
439                 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
440                 exit (1);
441         }
442
443         listen_fd = sock;
444
445         ipc_filename = g_strdup (filename);
446
447         server_uri = g_strdup_printf ("unix://%s/.mono-%"PRIdMAX"?/vm", directory, (intmax_t) getpid ());
448
449         g_free (filename);
450         g_free (directory);
451 }
452
453 static void
454 transport_connect (void)
455 {
456         ipc_connect ();
457 }
458
459 #if 0
460
461 static void
462 transport_send (int fd, guint8 *data, int len)
463 {
464         int res;
465
466         stats.bytes_sent += len;
467         //printf ("X: %d\n", stats.bytes_sent);
468
469         res = write (fd, data, len);
470         if (res != len) {
471                 /* FIXME: What to do here ? */
472         }
473 }
474
475 #endif
476
477 static void
478 transport_start_receive (void)
479 {
480         transport_connect ();
481
482         if (!listen_fd)
483                 return;
484
485         receiver_thread_handle = mono_threads_create_thread (receiver_thread, NULL, NULL, NULL);
486         g_assert (receiver_thread_handle);
487 }
488
489 static gsize WINAPI
490 receiver_thread (void *arg)
491 {
492         MonoError error;
493         int res, content_len;
494         guint8 buffer [256];
495         guint8 *p, *p_end;
496         MonoObject *exc;
497
498         mono_native_thread_set_name (mono_native_thread_id_get (), "Attach receiver");
499
500         printf ("attach: Listening on '%s'...\n", server_uri);
501
502         while (TRUE) {
503                 conn_fd = accept (listen_fd, NULL, NULL);
504                 if (conn_fd == -1)
505                         /* Probably closed by mono_attach_cleanup () */
506                         return 0;
507
508                 printf ("attach: Connected.\n");
509
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;
517
518                 while (TRUE) {
519                         char *cmd, *agent_name, *agent_args;
520                         guint8 *body;
521
522                         /* Read Header */
523                         res = read (conn_fd, buffer, 6);
524
525                         if (res == -1 && errno == EINTR)
526                                 continue;
527
528                         if (res == -1 || stop_receiver_thread)
529                                 break;
530
531                         if (res != 6)
532                                 break;
533
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");
536                                 break;
537                         }
538
539                         /* Read content length */
540                         res = read (conn_fd, buffer, 4);
541                         if (res != 4)
542                                 break;
543
544                         p = buffer;
545                         p_end = p + 8;
546
547                         content_len = decode_int (p, &p, p_end);
548
549                         /* Read message body */
550                         body = (guint8 *)g_malloc (content_len);
551                         res = read (conn_fd, body, content_len);
552                         
553                         p = body;
554                         p_end = body + content_len;
555
556                         cmd = decode_string_value (p, &p, p_end);
557                         if (cmd == NULL)
558                                 break;
559                         g_assert (!strcmp (cmd, "attach"));
560
561                         agent_name = decode_string_value (p, &p, p_end);
562                         agent_args = decode_string_value (p, &p, p_end);
563
564                         printf ("attach: Loading agent '%s'.\n", agent_name);
565                         mono_attach_load_agent (mono_domain_get (), agent_name, agent_args, &exc);
566
567                         g_free (body);
568
569                         // FIXME: Send back a result
570                 }
571
572                 close (conn_fd);
573                 conn_fd = 0;
574
575                 printf ("attach: Disconnected.\n");
576
577                 if (stop_receiver_thread)
578                         break;
579         }
580
581         return 0;
582 }
583
584 #else /* DISABLE_ATTACH */
585
586 void
587 mono_attach_parse_options (char *options)
588 {
589 }
590
591 void
592 mono_attach_init (void)
593 {
594 }
595
596 gboolean
597 mono_attach_start (void)
598 {
599         return FALSE;
600 }
601
602 void
603 mono_attach_maybe_start (void)
604 {
605 }
606
607 void
608 mono_attach_cleanup (void)
609 {
610 }
611
612 #endif /* DISABLE_ATTACH */