In my default (full-trust) AppDomain I want to create a sandbox AppDomain and subscribe to an event in it:
class Domain : MarshalByRefObject
{
public event Action TestEvent;
}
Domain domain = AppDomainStarter.Start<Domain>(@"C:\Temp", "Domain", null, true);
domain.TestEvent += () => { }; // SecurityException
Subscription fails with the message "Request for the permission of type 'System.Security.Permissions.ReflectionPermission, mscorlib, Version=4.0.0.0...' failed."
(For the definition of AppDomainStarter, see my answer to another question.)
Note that the ApplicationBase
C:\Temp is NOT the folder that contains the assembly that contains Domain. This is deliberate; my goal is to load a second 3rd-party untrusted assembly inside the new AppDomain, and this second assembly is located in C:\Temp (or anywhere else, maybe even a network share). But before I can load the second assembly I need to load my Domain
class inside the new AppDomain. Doing so succeeds, but for some reason I cannot subscribe to an event across the AppDomain boundary (I can call methods, but not subscribe to events).
UPDATE: Evidently, when subscribing to an event in a sandbox AppDomain, both the subscriber method and the class that contains the subscriber must be public. For example:
public static class Program
{
class Domain : MarshalByRefObject
{
public event Action TestEvent;
public Domain() { Console.WriteLine("Domain created OK"); }
}
static void Main()
{
string loc = @"C:\Temp";
Domain domain = AppDomainStarter.Start<Domain>(loc, "Domain", null, true);
// DIFFERENT EXCEPTION THIS TIME!
domain.TestEvent += new Action(domain_TestEvent);
}
public static void domain_TestEvent() { }
}
However, I STILL can't subscribe to the event. The new error is "Could not load file or assembly 'TestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."
In a way, this makes sense because I specified the "wrong" folder "C:\Temp" as the ApplicationBase of my new AppDomain, but in a way this makes no sense whatsoever because the "TestApp" assembly is already loaded in both AppDomains. How is it possible that the CLR cannot find an assembly that is already loaded?
Moreover, it makes no difference if I add permission to access the folder that contains my assembly:
string folderOfT = Path.GetFullPath(Path.Combine(typeof(T).Assembly.Location, ".."));
permSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, folderOfT));
// Same exception still occurs
I can "fix" the problem using a different value for AppDomainSetup.ApplicationBase
:
string loc = Path.GetFullPath(Assembly.GetExecutingAssembly().Location + @"\..");
This eliminates the exception, but I can't use this "solution" because the purpose of the AppDomain is to load an untrusted assembly from a different folder than the folder that contains my own assembly. Therefore, loc
must be the folder that contains the untrusted assembly, not the one that contains my assembly.
The exception is from the partial trust domain not the full trust one. You must have omitted to grant the ReflectionPermission in the partial trust domain
Assemblies are resolved on a per-AppDomain basis. Since you are starting the new AppDomain in a different directory (and your assembly isn't registered in the GAC), it can't locate the assembly. You can modify your AppDomainStarter code to first load the targeted assembly, then create an instance from that assembly:
Assembly assembly = Assembly.LoadFrom(typeof(T).Assembly.ManifestModule.FullyQualifiedName);
return (T)assembly.CreateInstance(typeof(T).FullName, false, 0, null, constructorArgs, null, null);
The only thing I could find that works is to put the assembly (that contains the code that you want to run in a new AppDomain) into the GAC. Of course, this is a huge pain in the butt, but it's the only thing that works.
Below I will describe a couple of things I tried that did NOT work.
In some circumstances, Visual Studio 2010 will give you this message when calling Activator.CreateInstanceFrom (I'm not sure when exactly--a clean console app does not produce this):
Managed Debugging Assistant 'LoadFromContext' has detected a problem
in 'C:\Users...\TestApp.vshost.exe'. Additional Information: The
assembly named 'TestApp' was loaded from
'file:///C:/Users/.../TestApp.exe' using the LoadFrom context. The use
of this context can result in unexpected behavior for serialization,
casting and dependency resolution. In almost all cases, it is
recommended that the LoadFrom context be avoided. This can be done by
installing assemblies in the Global Assembly Cache or in the
ApplicationBase directory and using Assembly.Load when explicitly
loading assemblies.
The documentation of Assembly.LoadFrom includes this statement: "If an assembly is loaded with LoadFrom, and later an assembly in the load context attempts to load [the] same assembly by display name, the load attempt fails. This can occur when an assembly is de-serialized." Sadly, there is no hint about why this happens.
In the example code, an Assembly is not being deserialized (and I'm not totally sure what it means to deserialize an Assembly in the first place), but a delegate is being deserialized; it's reasonable to hypothesize that deserializing a delegate involves an attempt to load the same assembly "by display name".
If this were true, it would not be possible to pass a delegate across AppDomain boundaries if the delegate points to a function that is located in an Assembly that was loaded using the "LoadFrom context". In that case, using CreateInstance
instead of CreateInstanceFrom
could avoid this problem (because CreateInstanceFrom
uses LoadFrom
):
return (T)Activator.CreateInstance(newDomain,
typeof(T).Assembly.FullName,
typeof(T).FullName, false,
0, null, constructorArgs, null, null).Unwrap();
But this turns out to be a red herring; CreateInstance
cannot be used unless the ApplicationBase
is set to the folder that contains our assembly, and if ApplicationBase
IS set to that folder, then subscribing to TestEvent succeeds regardless of whether CreateInstance
or CreateInstanceFrom
was used to create T in the new AppDomain. Therefore, the fact that T was loaded via LoadFrom
does not cause the problem all by itself.
Another thing I tried was to sign the assembly and tell the .NET Framework that it should be given fulltrust:
newDomain = AppDomain.CreateDomain(appDomainName, null, setup, permSet,
new StrongName[] { GetStrongName(typeof(T).Assembly) });
This relies on the GetStrongName method from an MSDN article. Unfortunately, this has no effect (i.e. the FileNotFoundException still happens).