[remoting] Serialize exceptions between domains inside try/catch
authorVlad Brezae <brezaevlad@gmail.com>
Tue, 9 May 2017 21:54:47 +0000 (00:54 +0300)
committerVlad Brezae <brezaevlad@gmail.com>
Tue, 9 May 2017 21:54:47 +0000 (00:54 +0300)
If a remote invoke in another domain throws an exception, the xdomain-dispatch wrapper will serialize the exception and pass it over to the calling domain so that it can be rethrown there. Serializing the exception object can itself throw an exception which we didn't properly catch, leading to unwinding to the caller domain without changing the domain state back and wreaking havoc.

If the serialization of the original exception throws a new exception, this new exception takes its place, being passed instead to the caller domain.

mono/metadata/remoting.c
mono/tests/Makefile.am
mono/tests/appdomain-serialize-exception.cs [new file with mode: 0644]

index 9578b47e5d5ec61ae5ff9d8b2be8574fc485535d..ea2ff0c3d7b2fc029d42a882f828d220bee6258f 100644 (file)
@@ -671,8 +671,8 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in
        int i, j, param_index, copy_locals_base;
        MonoClass *ret_class = NULL;
        int loc_array=0, loc_return=0, loc_serialized_exc=0;
-       MonoExceptionClause *main_clause;
-       int pos, pos_leave;
+       MonoExceptionClause *clauses, *main_clause, *serialization_clause;
+       int pos, pos_leave, pos_leave_serialization;
        gboolean copy_return;
        WrapperInfo *info;
 
@@ -714,7 +714,8 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in
 
        /* try */
 
-       main_clause = (MonoExceptionClause *)mono_image_alloc0 (method->klass->image, sizeof (MonoExceptionClause));
+       clauses = (MonoExceptionClause *)mono_image_alloc0 (method->klass->image, 2 * sizeof (MonoExceptionClause));
+       main_clause = &clauses [0];
        main_clause->try_offset = mono_mb_get_label (mb);
 
        /* Clean the call context */
@@ -894,15 +895,48 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in
        
        /* handler code */
        main_clause->handler_offset = mono_mb_get_label (mb);
+
+       /*
+        * We deserialize the exception in another try-catch so we can catch
+        * serialization failure exceptions.
+        */
+       serialization_clause = &clauses [1];
+       serialization_clause->try_offset = mono_mb_get_label (mb);
+
+       mono_mb_emit_managed_call (mb, method_rs_serialize_exc, NULL);
+       mono_mb_emit_stloc (mb, loc_serialized_exc);
+       mono_mb_emit_ldarg (mb, 2);
+       mono_mb_emit_ldloc (mb, loc_serialized_exc);
+       mono_mb_emit_byte (mb, CEE_STIND_REF);
+       pos_leave_serialization = mono_mb_emit_branch (mb, CEE_LEAVE);
+
+       /* Serialization exception catch */
+       serialization_clause->flags = MONO_EXCEPTION_CLAUSE_NONE;
+       serialization_clause->try_len = mono_mb_get_pos (mb) - serialization_clause->try_offset;
+       serialization_clause->data.catch_class = mono_defaults.object_class;
+
+       /* handler code */
+       serialization_clause->handler_offset = mono_mb_get_label (mb);
+
+       /*
+        * If the serialization of the original exception failed we serialize the newly
+        * thrown exception, which should always succeed, passing it over to the calling
+        * domain.
+        */
        mono_mb_emit_managed_call (mb, method_rs_serialize_exc, NULL);
        mono_mb_emit_stloc (mb, loc_serialized_exc);
        mono_mb_emit_ldarg (mb, 2);
        mono_mb_emit_ldloc (mb, loc_serialized_exc);
        mono_mb_emit_byte (mb, CEE_STIND_REF);
        mono_mb_emit_branch (mb, CEE_LEAVE);
-       main_clause->handler_len = mono_mb_get_pos (mb) - main_clause->handler_offset;
-       /* end catch */
 
+       /* end serialization exception catch */
+       serialization_clause->handler_len = mono_mb_get_pos (mb) - serialization_clause->handler_offset;
+       mono_mb_patch_branch (mb, pos_leave_serialization);
+
+       mono_mb_emit_branch (mb, CEE_LEAVE);
+       /* end main catch */
+       main_clause->handler_len = mono_mb_get_pos (mb) - main_clause->handler_offset;
        mono_mb_patch_branch (mb, pos_leave);
        
        if (copy_return)
@@ -910,7 +944,7 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in
 
        mono_mb_emit_byte (mb, CEE_RET);
 
-       mono_mb_set_clauses (mb, 1, main_clause);
+       mono_mb_set_clauses (mb, 2, clauses);
 #endif
 
        info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
index f8946b03be7cbd909b4c31d8b448741bcf8a01b3..fc1259252f3d9750bb61c8073b87f69f36a433dd 100644 (file)
@@ -458,6 +458,7 @@ TESTS_CS_SRC=               \
        appdomain1.cs   \
        appdomain2.cs   \
        appdomain-exit.cs       \
+       appdomain-serialize-exception.cs \
        assemblyresolve_event2.2.cs     \
        appdomain-unload-callback.cs    \
        appdomain-unload-doesnot-raise-pending-events.cs        \
diff --git a/mono/tests/appdomain-serialize-exception.cs b/mono/tests/appdomain-serialize-exception.cs
new file mode 100644 (file)
index 0000000..8b0fe47
--- /dev/null
@@ -0,0 +1,45 @@
+using System;
+using System.Reflection;
+using System.Runtime.Serialization;
+
+public class UnserializableException : Exception
+{
+}
+
+public class TestOutput : MarshalByRefObject
+{
+       public void ThrowUnserializable ()
+       {
+               Console.WriteLine("Throwing Unserializable exception in AppDomain \"{0}\"", AppDomain.CurrentDomain.FriendlyName);
+               throw new UnserializableException ();
+       }
+}
+
+public class Example
+{
+       public static int Main ()
+       {
+               string original_domain = AppDomain.CurrentDomain.FriendlyName;
+
+               AppDomain ad = AppDomain.CreateDomain("subdomain");
+               try {
+                       TestOutput remoteOutput = (TestOutput) ad.CreateInstanceAndUnwrap(
+                               typeof (TestOutput).Assembly.FullName,
+                               "TestOutput");
+                       remoteOutput.ThrowUnserializable ();
+               } catch (SerializationException) {
+                       Console.WriteLine ("Caught serialization exception");
+               } catch (Exception) {
+                       Console.WriteLine ("Caught other exception");
+                       Environment.Exit (1);
+               } finally {
+                       Console.WriteLine ("Finally in domain {0}", AppDomain.CurrentDomain.FriendlyName);
+                       if (original_domain != AppDomain.CurrentDomain.FriendlyName)
+                               Environment.Exit (2);
+                       AppDomain.Unload (ad);
+               }
+
+               Console.WriteLine ("All OK");
+               return 0;
+       }
+}