remove GenericVectorTests from rsp (#17366)
[mono-project.git] / mono / metadata / attach.c
blob72d994dea3b99c485dc8519499ab468b97439657
1 /**
2 * \file
3 * Support for attaching to the runtime from other processes.
5 * Author:
6 * Zoltan Varga (vargaz@gmail.com)
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.
12 #include <config.h>
13 #include <glib.h>
14 #include "attach.h"
16 #ifdef HOST_WIN32
17 #define DISABLE_ATTACH
18 #endif
19 #ifndef DISABLE_ATTACH
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/un.h>
28 #include <netinet/in.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-internals.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>
44 #include <mono/utils/w32api.h>
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.
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.
71 typedef struct {
72 gboolean enabled;
73 } AgentConfig;
75 typedef struct {
76 int bytes_sent;
77 } AgentStats;
79 /*******************************************************************/
80 /* Remoting Protocol type definitions from [MS-NRBF] and [MS-NRTP] */
81 /*******************************************************************/
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;
90 static AgentConfig config;
92 static int listen_fd, conn_fd;
94 static char *ipc_filename;
96 static char *server_uri;
98 static MonoThreadHandle *receiver_thread_handle;
100 static volatile gboolean stop_receiver_thread;
102 static gboolean needs_to_start, started;
104 static void transport_connect (void);
106 static gsize WINAPI receiver_thread (void *arg);
108 static void transport_start_receive (void);
111 * Functions to decode protocol data
113 static int
114 decode_byte (guint8 const *buf, guint8 const **endbuf, guint8 const *limit)
116 *endbuf = buf + 1;
117 g_assert (*endbuf <= limit);
118 return buf [0];
121 static int
122 decode_int (const guint8 *buf)
124 return (((int)buf [0]) << 0) | (((int)buf [1]) << 8) | (((int)buf [2]) << 16) | (((int)buf [3]) << 24);
127 static const char*
128 decode_string_value (guint8 const *buf, guint8 const **endbuf, guint8 const *limit)
130 int type;
131 gint32 length;
132 guint8 const *p = buf;
134 type = decode_byte (p, &p, limit);
135 if (type == PRIM_TYPE_NULL) {
136 *endbuf = p;
137 return NULL;
139 g_assert (type == PRIM_TYPE_STRING);
141 length = 0;
142 while (TRUE) {
143 guint8 b = decode_byte (p, &p, limit);
145 length <<= 8;
146 length += b;
147 if (b <= 0x7f)
148 break;
151 g_assert (length < (1 << 16));
153 const char *s = (const char*)p;
154 p += length + 1;
156 g_assert (p <= limit);
158 *endbuf = p;
160 return s;
163 /********************************/
164 /* AGENT IMPLEMENTATION */
165 /********************************/
167 void
168 mono_attach_parse_options (char *options)
170 if (!options)
171 return;
172 if (!strcmp (options, "disable"))
173 config.enabled = FALSE;
176 void
177 mono_attach_init (void)
179 config.enabled = TRUE;
183 * mono_attach_start:
185 * Start the attach mechanism if needed. This is called from a signal handler so it must be signal safe.
187 * Returns: whenever it was started.
189 gboolean
190 mono_attach_start (void)
192 char path [256];
193 int fd;
195 if (started)
196 return FALSE;
198 /* Check for the existence of the trigger file */
201 * We don't do anything with this file, and the only thing an attacker can do
202 * by creating it is to enable the attach mechanism if the process receives a
203 * SIGQUIT signal, which can only be sent by the owner/root.
205 snprintf (path, sizeof (path), "/tmp/.mono_attach_pid%" PRIdMAX, (intmax_t) getpid ());
206 fd = open (path, O_RDONLY);
207 if (fd == -1)
208 return FALSE;
209 close (fd);
211 if (!config.enabled)
212 /* Act like we started */
213 return TRUE;
215 if (started)
216 return FALSE;
219 * Our startup includes non signal-safe code, so ask the finalizer thread to
220 * do the actual startup.
222 needs_to_start = TRUE;
223 mono_gc_finalize_notify ();
225 return TRUE;
228 /* Called by the finalizer thread when it is woken up */
229 void
230 mono_attach_maybe_start (void)
232 if (!needs_to_start)
233 return;
235 needs_to_start = FALSE;
236 if (!started) {
237 transport_start_receive ();
239 started = TRUE;
243 void
244 mono_attach_cleanup (void)
246 if (listen_fd)
247 close (listen_fd);
248 if (ipc_filename)
249 unlink (ipc_filename);
251 stop_receiver_thread = TRUE;
252 if (conn_fd)
253 /* This will cause receiver_thread () to break out of the read () call */
254 close (conn_fd);
256 /* Wait for the receiver thread to exit */
257 if (receiver_thread_handle)
258 mono_thread_info_wait_one_handle (receiver_thread_handle, 0, FALSE);
261 static int
262 mono_attach_load_agent (MonoDomain *domain, const char *agent, const char *args)
264 HANDLE_FUNCTION_ENTER ();
266 ERROR_DECL (error);
267 MonoAssembly *agent_assembly;
268 MonoImage *image;
269 MonoMethod *method;
270 guint32 entry;
271 MonoArrayHandle main_args;
272 gpointer pa [1];
273 MonoImageOpenStatus open_status;
274 int result = 0;
276 MonoAssemblyOpenRequest req;
277 mono_assembly_request_prepare_open (&req, MONO_ASMCTX_DEFAULT, mono_domain_default_alc (mono_domain_get ()));
278 agent_assembly = mono_assembly_request_open (agent, &req, &open_status);
279 if (!agent_assembly) {
280 fprintf (stderr, "Cannot open agent assembly '%s': %s.\n", agent, mono_image_strerror (open_status));
281 result = 2;
282 goto exit;
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_internal (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 result = 1;
294 goto exit;
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 result = 1;
301 goto exit;
304 main_args = mono_array_new_handle (domain, mono_defaults.string_class, (args == NULL) ? 0 : 1, error);
305 if (MONO_HANDLE_IS_NULL (main_args)) {
306 g_print ("Could not allocate main method args due to %s\n", mono_error_get_message (error));
307 result = 1;
308 goto exit;
311 if (args) {
312 MonoStringHandle args_str = mono_string_new_handle (domain, args, error);
313 if (!is_ok (error)) {
314 g_print ("Could not allocate main method arg string due to %s\n", mono_error_get_message (error));
315 result = 1;
316 goto exit;
318 MONO_HANDLE_ARRAY_SETREF (main_args, 0, args_str);
321 pa [0] = MONO_HANDLE_RAW (main_args);
322 MonoObject *exc;
323 mono_runtime_try_invoke (method, NULL, pa, &exc, error);
324 if (!is_ok (error)) {
325 g_print ("The entry point method of assembly '%s' could not be executed due to %s\n", agent, mono_error_get_message (error));
326 result = 1;
327 goto exit;
330 result = 0;
331 exit:
332 mono_error_cleanup (error);
333 HANDLE_FUNCTION_RETURN_VAL (result);
337 * ipc_connect:
339 * Create a UNIX domain socket and bind it to a file in /tmp.
341 * SECURITY: This routine is _very_ security critical since we depend on the UNIX
342 * permissions system to prevent attackers from connecting to the socket.
344 static void
345 ipc_connect (void)
347 struct sockaddr_un name;
348 int sock, res;
349 size_t size;
350 char *filename, *directory;
351 struct stat stat;
352 struct passwd pwbuf;
353 char buf [1024];
354 struct passwd *pw;
356 if (getuid () != geteuid ()) {
357 fprintf (stderr, "attach: disabled listening on an IPC socket when running in setuid mode.\n");
358 return;
361 /* Create the socket. */
362 sock = socket (PF_UNIX, SOCK_STREAM, 0);
363 if (sock < 0) {
364 perror ("attach: failed to create IPC socket");
365 return;
369 * For security reasons, create a directory to hold the listening socket,
370 * since there is a race between bind () and chmod () below.
372 /* FIXME: Use TMP ? */
373 pw = NULL;
374 #ifdef HAVE_GETPWUID_R
375 res = getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw);
376 #else
377 pw = getpwuid(getuid ());
378 res = pw != NULL ? 0 : 1;
379 #endif
380 if (res != 0) {
381 fprintf (stderr, "attach: getpwuid_r () failed.\n");
382 return;
384 g_assert (pw);
385 directory = g_strdup_printf ("/tmp/mono-%s", pw->pw_name);
386 res = mkdir (directory, S_IRUSR | S_IWUSR | S_IXUSR);
387 if (res != 0) {
388 if (errno == EEXIST) {
389 /* Check type and permissions */
390 res = lstat (directory, &stat);
391 if (res != 0) {
392 perror ("attach: lstat () failed");
393 return;
395 if (!S_ISDIR (stat.st_mode)) {
396 fprintf (stderr, "attach: path '%s' is not a directory.\n", directory);
397 return;
399 if (stat.st_uid != getuid ()) {
400 fprintf (stderr, "attach: directory '%s' is not owned by the current user.\n", directory);
401 return;
403 if ((stat.st_mode & S_IRWXG) != 0 || (stat.st_mode & S_IRWXO) || ((stat.st_mode & S_IRWXU) != (S_IRUSR | S_IWUSR | S_IXUSR))) {
404 fprintf (stderr, "attach: directory '%s' should have protection 0700.\n", directory);
405 return;
407 } else {
408 perror ("attach: mkdir () failed");
409 return;
413 filename = g_strdup_printf ("%s/.mono-%" PRIdMAX, directory, (intmax_t) getpid ());
414 unlink (filename);
416 /* Bind a name to the socket. */
417 name.sun_family = AF_UNIX;
418 strcpy (name.sun_path, filename);
420 size = (offsetof (struct sockaddr_un, sun_path)
421 + strlen (name.sun_path) + 1);
423 if (bind (sock, (struct sockaddr *) &name, size) < 0) {
424 fprintf (stderr, "attach: failed to bind IPC socket '%s': %s\n", filename, strerror (errno));
425 close (sock);
426 return;
429 /* Set permissions */
430 res = chmod (filename, S_IRUSR | S_IWUSR);
431 if (res != 0) {
432 perror ("attach: failed to set permissions on IPC socket");
433 close (sock);
434 unlink (filename);
435 return;
438 res = listen (sock, 16);
439 if (res != 0) {
440 fprintf (stderr, "attach: listen () failed: %s\n", strerror (errno));
441 exit (1);
444 listen_fd = sock;
446 ipc_filename = g_strdup (filename);
448 server_uri = g_strdup_printf ("unix://%s/.mono-%" PRIdMAX "?/vm", directory, (intmax_t) getpid ());
450 g_free (filename);
451 g_free (directory);
454 static void
455 transport_connect (void)
457 ipc_connect ();
460 #if 0
462 static void
463 transport_send (int fd, guint8 *data, int len)
465 int res;
467 stats.bytes_sent += len;
468 //printf ("X: %d\n", stats.bytes_sent);
470 res = write (fd, data, len);
471 if (res != len) {
472 /* FIXME: What to do here ? */
476 #endif
478 static void
479 transport_start_receive (void)
481 ERROR_DECL (error);
482 MonoInternalThread *internal;
484 transport_connect ();
486 if (!listen_fd)
487 return;
489 internal = mono_thread_create_internal (mono_get_root_domain (), (gpointer)receiver_thread, NULL, MONO_THREAD_CREATE_FLAGS_NONE, error);
490 mono_error_assert_ok (error);
492 receiver_thread_handle = mono_threads_open_thread_handle (internal->handle);
493 g_assert (receiver_thread_handle);
496 static gsize WINAPI
497 receiver_thread (void *arg)
499 MonoInternalThread *internal = mono_thread_internal_current ();
501 mono_thread_set_name_constant_ignore_error (internal, "Attach receiver", MonoSetThreadNameFlag_Permanent);
503 /* Ask the runtime to not abort this thread */
504 //internal->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
505 /* Ask the runtime to not wait for this thread */
506 internal->state |= ThreadState_Background;
508 printf ("attach: Listening on '%s'...\n", server_uri);
510 while (TRUE) {
511 conn_fd = accept (listen_fd, NULL, NULL);
512 if (conn_fd == -1)
513 /* Probably closed by mono_attach_cleanup () */
514 return 0;
516 printf ("attach: Connected.\n");
518 guint8* body = NULL;
520 while (TRUE) {
521 guint8 buffer [6];
523 /* Read Header */
524 int res = read (conn_fd, buffer, 6);
526 if (res == -1 && errno == EINTR)
527 continue;
529 if (res == -1 || stop_receiver_thread)
530 break;
532 if (res != 6)
533 break;
535 if (memcmp (buffer, "MONO", 4) != 0 || buffer [4] != 1 || buffer [5] != 0) {
536 fprintf (stderr, "attach: message from server has unknown header.\n");
537 break;
540 /* Read content length */
541 res = read (conn_fd, buffer, 4);
542 if (res != 4)
543 break;
545 const int content_len = decode_int (buffer);
547 /* Read message body */
548 body = (guint8 *)g_malloc (content_len);
549 res = read (conn_fd, body, content_len);
550 if (res != content_len)
551 break;
553 guint8 const * p = body;
554 guint8 const * const p_end = body + content_len;
556 char const * const cmd = decode_string_value (p, &p, p_end);
557 if (cmd == NULL)
558 break;
560 // 10: 7:attach\0 + one byte each for the types of cmd, name, args.
561 g_assert (content_len >= 10 && !memcmp (cmd, "attach", 7));
563 char const * const agent_name = decode_string_value (p, &p, p_end);
564 char const * const agent_args = decode_string_value (p, &p, p_end);
566 printf ("attach: Loading agent '%s'.\n", agent_name);
567 mono_attach_load_agent (mono_domain_get (), agent_name, agent_args);
569 g_free (body);
570 body = NULL;
572 // FIXME: Send back a result
575 g_free (body);
576 body = NULL;
578 close (conn_fd);
579 conn_fd = 0;
581 printf ("attach: Disconnected.\n");
583 if (stop_receiver_thread)
584 break;
587 return 0;
590 #else /* DISABLE_ATTACH */
592 void
593 mono_attach_parse_options (char *options)
597 void
598 mono_attach_init (void)
602 gboolean
603 mono_attach_start (void)
605 return FALSE;
608 void
609 mono_attach_maybe_start (void)
613 void
614 mono_attach_cleanup (void)
618 #endif /* DISABLE_ATTACH */