为什么我无法订阅部分信任 AppDomain 中的事件?
在我的默认(完全信任)AppDomain 中,我想创建一个沙箱 AppDomain 并订阅其中的事件:
class Domain : MarshalByRefObject
{
public event Action TestEvent;
}
Domain domain = AppDomainStarter.Start<Domain>(@"C:\Temp", "Domain", null, true);
domain.TestEvent += () => { }; // SecurityException
订阅失败,并显示消息“请求类型为‘System.Security.Permissions.ReflectionPermission, mscorlib, Version=4.0’的权限” .0.0...'失败了。”
(有关 AppDomainStarter 的定义,请参阅我对另一个问题的回答。)
请注意,ApplicationBase
C:\Temp 不是包含域的程序集的文件夹。这是故意的;我的目标是在新的 AppDomain 中加载第二个第三方不受信任的程序集,第二个程序集位于 C:\Temp(或其他任何地方,甚至可能是网络共享)。但在加载第二个程序集之前,我需要在新的 AppDomain 中加载我的 Domain
类。这样做成功了,但由于某种原因,我无法跨 AppDomain 边界订阅事件(我可以调用方法,但不能订阅事件)。
更新:显然,当订阅沙箱AppDomain中的事件时,订阅者方法和包含订阅者的类都必须是公共的。例如:
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() { }
}
但是,我仍然无法订阅该活动。新错误是“无法加载文件或程序集“TestApp,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”或其依赖项之一。系统找不到指定的文件。”
在某种程度上,这是有道理的,因为我指定了“错误”文件夹“C:\Temp”作为新 AppDomain 的 ApplicationBase,但在某种程度上,这毫无意义,因为“TestApp”程序集已加载在两个应用程序域中。 CLR 怎么可能找不到已经加载的程序集呢?
此外,如果我添加访问包含程序集的文件夹的权限也没有什么区别:
string folderOfT = Path.GetFullPath(Path.Combine(typeof(T).Assembly.Location, ".."));
permSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, folderOfT));
// Same exception still occurs
我可以使用 AppDomainSetup.ApplicationBase
使用不同的值来“修复”问题:
string loc = Path.GetFullPath(Assembly.GetExecutingAssembly().Location + @"\..");
这消除了异常,但我可以不要使用此“解决方案”,因为 AppDomain 的目的是从与包含我自己的程序集的文件夹不同的文件夹加载不受信任的程序集。因此,loc
必须是包含不受信任的程序集的文件夹,而不是包含我的程序集的文件夹。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
程序集是在每个 AppDomain 的基础上解析的。由于您在不同的目录中启动新的 AppDomain(并且您的程序集未在 GAC 中注册),因此它无法找到该程序集。您可以修改 AppDomainStarter 代码以首先加载目标程序集,然后从该程序集创建一个实例:
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:
我发现唯一可行的方法是将程序集(包含要在新 AppDomain 中运行的代码)放入 GAC 中。当然,这是一个巨大的痛苦,但这是唯一有效的方法。
下面我将描述一些我尝试过但不起作用的事情。
在某些情况下,Visual Studio 2010 在调用 Activator.CreateInstanceFrom 时会向您显示此消息(我不确定具体时间 - 干净的控制台应用程序不会生成此消息):
Assembly.LoadFrom 的文档包含以下语句:“如果程序集是使用 LoadFrom 加载,然后加载上下文中的程序集尝试通过显示名称加载相同的程序集,当程序集反序列化时,可能会发生这种情况。”遗憾的是,没有任何线索说明为什么会发生这种情况。
在示例代码中,Assembly 没有被反序列化(而且我不完全确定反序列化 Assembly 意味着什么),但是一个委托正在被反序列化;可以合理地假设反序列化委托涉及尝试“按显示名称”加载相同的程序集。
如果这是真的,并且委托指向位于使用“LoadFrom context”加载的程序集中的函数,则不可能跨 AppDomain 边界传递委托。在这种情况下,使用
CreateInstance
而不是CreateInstanceFrom
可以避免此问题(因为CreateInstanceFrom
使用LoadFrom
):但这结果是一个红鲱鱼;除非将
ApplicationBase
设置为包含我们的程序集的文件夹,并且如果将ApplicationBase
设置为该文件夹,则无法使用CreateInstance
,然后订阅无论是使用CreateInstance
还是CreateInstanceFrom
在新 AppDomain 中创建 T,TestEvent 都会成功。因此,T 是通过 LoadFrom 加载的这一事实本身并不会导致问题。我尝试的另一件事是对程序集进行签名并告诉 .NET Framework 应给予其完全信任:
这依赖于 一篇 MSDN 文章。不幸的是,这没有任何效果(即 FileNotFoundException 仍然发生)。
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):
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 ofCreateInstanceFrom
could avoid this problem (becauseCreateInstanceFrom
usesLoadFrom
):But this turns out to be a red herring;
CreateInstance
cannot be used unless theApplicationBase
is set to the folder that contains our assembly, and ifApplicationBase
IS set to that folder, then subscribing to TestEvent succeeds regardless of whetherCreateInstance
orCreateInstanceFrom
was used to create T in the new AppDomain. Therefore, the fact that T was loaded viaLoadFrom
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:
This relies on the GetStrongName method from an MSDN article. Unfortunately, this has no effect (i.e. the FileNotFoundException still happens).
例外情况是部分信任域而不是完全信任域。您必须忽略在部分信任域中授予 ReflectionPermission
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