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