1 /**
2 * \file
3 * Support for attaching to the runtime from other processes.
4 *
5 * Author:
6 * Zoltan Varga (vargaz@gmail.com)
7 *
8 * Copyright 2007-2009 Novell, Inc (http://www.novell.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
10 */
11
12 #include <config.h>
13 #include <glib.h>
14
15 #ifdef HOST_WIN32
16 #define DISABLE_ATTACH
17 #endif
18 #ifndef DISABLE_ATTACH
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <sys/stat.h>
26 #include <sys/un.h>
27 #include <netinet/in.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <pwd.h>
31 #include <errno.h>
32 #include <netdb.h>
33 #include <unistd.h>
34
35 #include <mono/metadata/assembly-internals.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-internals.h>
41 #include <mono/utils/mono-threads.h>
42 #include "attach.h"
43
44 #include <mono/utils/w32api.h>
45
46 /*
47 * This module enables other processes to attach to a running mono process and
48 * load agent assemblies.
49 * Communication is done through a UNIX Domain Socket located at
50 * /tmp/mono-<USER>/.mono-<PID>.
51 * We use a simplified version of the .net remoting protocol.
52 * To increase security, and to avoid spinning up a listener thread on startup,
53 * we follow the java implementation, and only start up the attach mechanism
54 * when we receive a QUIT signal and there is a file named
55 * '.mono_attach_pid<PID>' in /tmp.
56 *
57 * SECURITY:
58 * - This module allows loading of arbitrary code into a running mono runtime, so
59 * it is security critical.
60 * - Security is based on controlling access to the unix file to which the unix
61 * domain socket is bound. Permissions/ownership are set such that only the owner
62 * of the process can access the socket.
63 * - As an additional measure, the socket is only created when the process receives
64 * a SIGQUIT signal, which only its owner/root can send.
65 * - The socket is kept in a directory whose ownership is checked before creating
66 * the socket. This could allow an attacker a kind of DOS attack by creating the
67 * directory with the wrong permissions/ownership. However, the only thing such
68 * an attacker could prevent is the attaching of agents to the mono runtime.
69 */
70
71 typedef struct {
72 gboolean enabled;
73 } AgentConfig;
74
75 typedef struct {
76 int bytes_sent;
77 } AgentStats;
78
79 /*******************************************************************/
80 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
81 /*******************************************************************/
82
83 typedef enum {
84 PRIM_TYPE_INT32 = 8,
85 PRIM_TYPE_INT64 = 9,
86 PRIM_TYPE_NULL = 17,
87 PRIM_TYPE_STRING = 18
88 } PrimitiveType;
89
90 static AgentConfig config;
91
92 static int listen_fd, conn_fd;
93
94 static char *ipc_filename;
95
96 static char *server_uri;
97
98 static MonoThreadHandle *receiver_thread_handle;
99
100 static gboolean stop_receiver_thread;
101
102 static gboolean needs_to_start, started;
103
104 static void transport_connect (void);
105
106 static gsize WINAPI receiver_thread (void *arg);
107
108 static void transport_start_receive (void);
109
110 /*
111 * Functions to decode protocol data
112 */
113 static inline int
decode_byte(guint8 * buf,guint8 ** endbuf,guint8 * limit)114 decode_byte (guint8 *buf, guint8 **endbuf, guint8 *limit)
115 {
116 *endbuf = buf + 1;
117 g_assert (*endbuf <= limit);
118 return buf [0];
119 }
120
121 static inline int
decode_int(guint8 * buf,guint8 ** endbuf,guint8 * limit)122 decode_int (guint8 *buf, guint8 **endbuf, guint8 *limit)
123 {
124 *endbuf = buf + 4;
125 g_assert (*endbuf <= limit);
126
127 return (((int)buf [0]) << 0) | (((int)buf [1]) << 8) | (((int)buf [2]) << 16) | (((int)buf [3]) << 24);
128 }
129
130 static char*
decode_string_value(guint8 * buf,guint8 ** endbuf,guint8 * limit)131 decode_string_value (guint8 *buf, guint8 **endbuf, guint8 *limit)
132 {
133 int type;
134 gint32 length;
135 guint8 *p = buf;
136 char *s;
137
138 type = decode_byte (p, &p, limit);
139 if (type == PRIM_TYPE_NULL) {
140 *endbuf = p;
141 return NULL;
142 }
143 g_assert (type == PRIM_TYPE_STRING);
144
145 length = 0;
146 while (TRUE) {
147 guint8 b = decode_byte (p, &p, limit);
148
149 length <<= 8;
150 length += b;
151 if (b <= 0x7f)
152 break;
153 }
154
155 g_assert (length < (1 << 16));
156
157 s = (char *)g_malloc (length + 1);
158
159 g_assert (p + length <= limit);
160 memcpy (s, p, length);
161 s [length] = '\0';
162 p += length;
163
164 *endbuf = p;
165
166 return s;
167 }
168
169 /********************************/
170 /* AGENT IMPLEMENTATION */
171 /********************************/
172
173 void
mono_attach_parse_options(char * options)174 mono_attach_parse_options (char *options)
175 {
176 if (!options)
177 return;
178 if (!strcmp (options, "disable"))
179 config.enabled = FALSE;
180 }
181
182 void
mono_attach_init(void)183 mono_attach_init (void)
184 {
185 config.enabled = TRUE;
186 }
187
188 /**
189 * mono_attach_start:
190 *
191 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
192 *
193 * Returns: whenever it was started.
194 */
195 gboolean
mono_attach_start(void)196 mono_attach_start (void)
197 {
198 char path [256];
199 int fd;
200
201 if (started)
202 return FALSE;
203
204 /* Check for the existence of the trigger file */
205
206 /*
207 * We don't do anything with this file, and the only thing an attacker can do
208 * by creating it is to enable the attach mechanism if the process receives a
209 * SIGQUIT signal, which can only be sent by the owner/root.
210 */
211 snprintf (path, sizeof (path), "/tmp/.mono_attach_pid%"PRIdMAX"", (intmax_t) getpid ());
212 fd = open (path, O_RDONLY);
213 if (fd == -1)
214 return FALSE;
215 close (fd);
216
217 if (!config.enabled)
218 /* Act like we started */
219 return TRUE;
220
221 if (started)
222 return FALSE;
223
224 /*
225 * Our startup includes non signal-safe code, so ask the finalizer thread to
226 * do the actual startup.
227 */
228 needs_to_start = TRUE;
229 mono_gc_finalize_notify ();
230
231 return TRUE;
232 }
233
234 /* Called by the finalizer thread when it is woken up */
235 void
mono_attach_maybe_start(void)236 mono_attach_maybe_start (void)
237 {
238 if (!needs_to_start)
239 return;
240
241 needs_to_start = FALSE;
242 if (!started) {
243 transport_start_receive ();
244
245 started = TRUE;
246 }
247 }
248
249 void
mono_attach_cleanup(void)250 mono_attach_cleanup (void)
251 {
252 if (listen_fd)
253 close (listen_fd);
254 if (ipc_filename)
255 unlink (ipc_filename);
256
257 stop_receiver_thread = TRUE;
258 if (conn_fd)
259 /* This will cause receiver_thread () to break out of the read () call */
260 close (conn_fd);
261
262 /* Wait for the receiver thread to exit */
263 if (receiver_thread_handle)
264 mono_thread_info_wait_one_handle (receiver_thread_handle, 0, FALSE);
265 }
266
267 static int
mono_attach_load_agent(MonoDomain * domain,char * agent,char * args,MonoObject ** exc)268 mono_attach_load_agent (MonoDomain *domain, char *agent, char *args, MonoObject **exc)
269 {
270 MonoError error;
271 MonoAssembly *agent_assembly;
272 MonoImage *image;
273 MonoMethod *method;
274 guint32 entry;
275 MonoArray *main_args;
276 gpointer pa [1];
277 MonoImageOpenStatus open_status;
278
279 agent_assembly = mono_assembly_open_predicate (agent, FALSE, FALSE, NULL, NULL, &open_status);
280 if (!agent_assembly) {
281 fprintf (stderr, "Cannot open agent assembly '%s': %s.\n", agent, mono_image_strerror (open_status));
282 g_free (agent);
283 return 2;
284 }
285
286 /*
287 * Can't use mono_jit_exec (), as it sets things which might confuse the
288 * real Main method.
289 */
290 image = mono_assembly_get_image (agent_assembly);
291 entry = mono_image_get_entry_point (image);
292 if (!entry) {
293 g_print ("Assembly '%s' doesn't have an entry point.\n", mono_image_get_filename (image));
294 g_free (agent);
295 return 1;
296 }
297
298 method = mono_get_method_checked (image, entry, NULL, NULL, &error);
299 if (method == NULL){
300 g_print ("The entry point method of assembly '%s' could not be loaded due to %s\n", agent, mono_error_get_message (&error));
301 mono_error_cleanup (&error);
302 g_free (agent);
303 return 1;
304 }
305
306
307 main_args = (MonoArray*)mono_array_new_checked (domain, mono_defaults.string_class, (args == NULL) ? 0 : 1, &error);
308 if (main_args == NULL) {
309 g_print ("Could not allocate main method args due to %s\n", mono_error_get_message (&error));
310 mono_error_cleanup (&error);
311 g_free (agent);
312 return 1;
313 }
314
315 if (args) {
316 MonoString *args_str = mono_string_new_checked (domain, args, &error);
317 if (!is_ok (&error)) {
318 g_print ("Could not allocate main method arg string due to %s\n", mono_error_get_message (&error));
319 mono_error_cleanup (&error);
320 g_free (agent);
321 return 1;
322 }
323 mono_array_set (main_args, MonoString*, 0, args_str);
324 }
325
326
327 pa [0] = main_args;
328 mono_runtime_try_invoke (method, NULL, pa, exc, &error);
329 if (!is_ok (&error)) {
330 g_print ("The entry point method of assembly '%s' could not be executed due to %s\n", agent, mono_error_get_message (&error));
331 mono_error_cleanup (&error);
332 g_free (agent);
333 return 1;
334 }
335
336 g_free (agent);
337
338 return 0;
339 }
340
341 /*
342 * ipc_connect:
343 *
344 * Create a UNIX domain socket and bind it to a file in /tmp.
345 *
346 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
347 * permissions system to prevent attackers from connecting to the socket.
348 */
349 static void
ipc_connect(void)350 ipc_connect (void)
351 {
352 struct sockaddr_un name;
353 int sock, res;
354 size_t size;
355 char *filename, *directory;
356 struct stat stat;
357 struct passwd pwbuf;
358 char buf [1024];
359 struct passwd *pw;
360
361 if (getuid () != geteuid ()) {
362 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
363 return;
364 }
365
366 /* Create the socket. */
367 sock = socket (PF_UNIX, SOCK_STREAM, 0);
368 if (sock < 0) {
369 perror ("attach: failed to create IPC socket");
370 return;
371 }
372
373 /*
374 * For security reasons, create a directory to hold the listening socket,
375 * since there is a race between bind () and chmod () below.
376 */
377 /* FIXME: Use TMP ? */
378 pw = NULL;
379 #ifdef HAVE_GETPWUID_R
380 res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
381 #else
382 pw = getpwuid(getuid ());
383 res = pw != NULL ? 0 : 1;
384 #endif
385 if (res != 0) {
386 fprintf (stderr, "attach: getpwuid_r () failed.\n");
387 return;
388 }
389 g_assert (pw);
390 directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
391 res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
392 if (res != 0) {
393 if (errno == EEXIST) {
394 /* Check type and permissions */
395 res = lstat (directory, &stat);
396 if (res != 0) {
397 perror ("attach: lstat () failed");
398 return;
399 }
400 if (!S_ISDIR (stat.st_mode)) {
401 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
402 return;
403 }
404 if (stat.st_uid != getuid ()) {
405 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
406 return;
407 }
408 if ((stat.st_mode & S_IRWXG) != 0 || (stat.st_mode & S_IRWXO) || ((stat.st_mode & S_IRWXU) != (S_IRUSR | S_IWUSR | S_IXUSR))) {
409 fprintf (stderr, "attach: directory '%s' should have protection 0700.\n", directory);
410 return;
411 }
412 } else {
413 perror ("attach: mkdir () failed");
414 return;
415 }
416 }
417
418 filename = g_strdup_printf ("%s/.mono-%"PRIdMAX"", directory, (intmax_t) getpid ());
419 unlink (filename);
420
421 /* Bind a name to the socket. */
422 name.sun_family = AF_UNIX;
423 strcpy (name.sun_path, filename);
424
425 size = (offsetof (struct sockaddr_un, sun_path)
426 + strlen (name.sun_path) + 1);
427
428 if (bind (sock, (struct sockaddr *) &name, size) < 0) {
429 fprintf (stderr, "attach: failed to bind IPC socket '%s': %s\n", filename, strerror (errno));
430 close (sock);
431 return;
432 }
433
434 /* Set permissions */
435 res = chmod (filename, S_IRUSR | S_IWUSR);
436 if (res != 0) {
437 perror ("attach: failed to set permissions on IPC socket");
438 close (sock);
439 unlink (filename);
440 return;
441 }
442
443 res = listen (sock, 16);
444 if (res != 0) {
445 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
446 exit (1);
447 }
448
449 listen_fd = sock;
450
451 ipc_filename = g_strdup (filename);
452
453 server_uri = g_strdup_printf ("unix://%s/.mono-%"PRIdMAX"?/vm", directory, (intmax_t) getpid ());
454
455 g_free (filename);
456 g_free (directory);
457 }
458
459 static void
transport_connect(void)460 transport_connect (void)
461 {
462 ipc_connect ();
463 }
464
465 #if 0
466
467 static void
468 transport_send (int fd, guint8 *data, int len)
469 {
470 int res;
471
472 stats.bytes_sent += len;
473 //printf ("X: %d\n", stats.bytes_sent);
474
475 res = write (fd, data, len);
476 if (res != len) {
477 /* FIXME: What to do here ? */
478 }
479 }
480
481 #endif
482
483 static void
transport_start_receive(void)484 transport_start_receive (void)
485 {
486 MonoError error;
487 MonoInternalThread *internal;
488
489 transport_connect ();
490
491 if (!listen_fd)
492 return;
493
494 internal = mono_thread_create_internal (mono_get_root_domain (), receiver_thread, NULL, MONO_THREAD_CREATE_FLAGS_NONE, &error);
495 mono_error_assert_ok (&error);
496
497 receiver_thread_handle = mono_threads_open_thread_handle (internal->handle);
498 g_assert (receiver_thread_handle);
499 }
500
501 static gsize WINAPI
receiver_thread(void * arg)502 receiver_thread (void *arg)
503 {
504 MonoError error;
505 int res, content_len;
506 guint8 buffer [256];
507 guint8 *p, *p_end;
508 MonoObject *exc;
509 MonoInternalThread *internal;
510
511 internal = mono_thread_internal_current ();
512 MonoString *attach_str = mono_string_new_checked (mono_domain_get (), "Attach receiver", &error);
513 mono_error_assert_ok (&error);
514 mono_thread_set_name_internal (internal, attach_str, TRUE, FALSE, &error);
515 mono_error_assert_ok (&error);
516 /* Ask the runtime to not abort this thread */
517 //internal->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
518 /* Ask the runtime to not wait for this thread */
519 internal->state |= ThreadState_Background;
520
521 printf ("attach: Listening on '%s'...\n", server_uri);
522
523 while (TRUE) {
524 conn_fd = accept (listen_fd, NULL, NULL);
525 if (conn_fd == -1)
526 /* Probably closed by mono_attach_cleanup () */
527 return 0;
528
529 printf ("attach: Connected.\n");
530
531 while (TRUE) {
532 char *cmd, *agent_name, *agent_args;
533 guint8 *body;
534
535 /* Read Header */
536 res = read (conn_fd, buffer, 6);
537
538 if (res == -1 && errno == EINTR)
539 continue;
540
541 if (res == -1 || stop_receiver_thread)
542 break;
543
544 if (res != 6)
545 break;
546
547 if ((strncmp ((char*)buffer, "MONO", 4) != 0) || buffer [4] != 1 || buffer [5] != 0) {
548 fprintf (stderr, "attach: message from server has unknown header.\n");
549 break;
550 }
551
552 /* Read content length */
553 res = read (conn_fd, buffer, 4);
554 if (res != 4)
555 break;
556
557 p = buffer;
558 p_end = p + 8;
559
560 content_len = decode_int (p, &p, p_end);
561
562 /* Read message body */
563 body = (guint8 *)g_malloc (content_len);
564 res = read (conn_fd, body, content_len);
565
566 p = body;
567 p_end = body + content_len;
568
569 cmd = decode_string_value (p, &p, p_end);
570 if (cmd == NULL)
571 break;
572 g_assert (!strcmp (cmd, "attach"));
573
574 agent_name = decode_string_value (p, &p, p_end);
575 agent_args = decode_string_value (p, &p, p_end);
576
577 printf ("attach: Loading agent '%s'.\n", agent_name);
578 mono_attach_load_agent (mono_domain_get (), agent_name, agent_args, &exc);
579
580 g_free (body);
581
582 // FIXME: Send back a result
583 }
584
585 close (conn_fd);
586 conn_fd = 0;
587
588 printf ("attach: Disconnected.\n");
589
590 if (stop_receiver_thread)
591 break;
592 }
593
594 return 0;
595 }
596
597 #else /* DISABLE_ATTACH */
598
599 void
mono_attach_parse_options(char * options)600 mono_attach_parse_options (char *options)
601 {
602 }
603
604 void
mono_attach_init(void)605 mono_attach_init (void)
606 {
607 }
608
609 gboolean
mono_attach_start(void)610 mono_attach_start (void)
611 {
612 return FALSE;
613 }
614
615 void
mono_attach_maybe_start(void)616 mono_attach_maybe_start (void)
617 {
618 }
619
620 void
mono_attach_cleanup(void)621 mono_attach_cleanup (void)
622 {
623 }
624
625 #endif /* DISABLE_ATTACH */
626