为啥我不能订阅部分信任 AppDomain 中的事件?

Posted

技术标签:

【中文标题】为啥我不能订阅部分信任 AppDomain 中的事件?【英文标题】:Why can't I subscribe to an event in a partial-trust AppDomain?为什么我不能订阅部分信任 AppDomain 中的事件? 【发布时间】:2012-01-22 19:11:54 【问题描述】:

在我的默认(完全信任)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的定义见my answer to another question。)

请注意,ApplicationBase C:\TempNOT 包含包含域的程序集的文件夹。这是故意的;我的目标是在新的 AppDomain 中加载第二个第 3 方不受信任的程序集,第二个程序集位于 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”程序集是 已在两个 AppDomain 中加载。 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 必须是包含不受信任程序集的文件夹,而不是包含我的程序集的文件夹。

【问题讨论】:

【参考方案1】:

例外是来自部分信任域而不是完全信任域。您必须忽略在部分信任域中授予 ReflectionPermission

【讨论】:

(i) 我没有尝试进行任何反射,(ii) ReflectionPermission 授予访问任何可访问类型的私有成员的访问权限; AFAIK 不需要访问公共成员。我不想授予 ReflectionPermission,因为它可能会打开安全漏洞。 (i) 如您所见,它不一定是您的代码,而是需要执行此操作的基础设施(这就是为什么公开类型解决了该问题)ii)足够公平,您已经解决了还是有问题 不能将“插件”文件夹设为现有appbase的子文件夹吗?那么您无需更改应用程序域的应用程序库,程序集将正确加载。您始终可以通过 FileIOPermission 将文件 IO 限制到插件目录 嗯,这实际上不是传统的插件方案。它是一个自动/交互式测试组装运行器。用户可以使用完全信任,但我想允许部分信任作为选项。应该可以从任何地方、任何驱动器或网络运行测试程序集。【参考方案2】:

程序集是在每个 AppDomain 的基础上解决的。由于您在不同的目录中启动新的 AppDomain(并且您的程序集未在 GAC 中注册),因此它无法找到该程序集。您可以修改 AppDomainStarter 代码以首先加载目标程序集,然后从该程序集创建一个实例:

            Assembly assembly = Assembly.LoadFrom(typeof(T).Assembly.ManifestModule.FullyQualifiedName);
        return (T)assembly.CreateInstance(typeof(T).FullName, false, 0, null, constructorArgs, null, null);

【讨论】:

您发布的代码会将程序集加载到与调用者相同的 AppDomain 中,而不是在新的 AppDomain 中,而且相关程序集已经加载(T在这种情况下是 Program.Domain,并且 Program.Main() 已经在调用堆栈上!)。【参考方案3】:

我发现唯一可行的方法是将程序集(包含要在新 AppDomain 中运行的代码)放入 GAC。当然,这是一个巨大的痛苦,但它是唯一有效的方法。

下面我将描述一些我尝试过但不起作用的事情。

在某些情况下,Visual Studio 2010 会在调用 Activator.CreateInstanceFrom 时向您显示此消息(我不确定确切的时间——干净的控制台应用程序不会产生此消息):

托管调试助手“LoadFromContext”检测到问题 在“C:\Users...\TestApp.vshost.exe”中。附加信息: 名为“TestApp”的程序集是从 'file:///C:/Users/.../TestApp.exe' 使用 LoadFrom 上下文。使用 这种上下文可能导致序列化的意外行为, 强制转换和依赖解析。在几乎所有情况下,它都是 建议避免使用 LoadFrom 上下文。这可以通过 在全局程序集缓存或 ApplicationBase 目录并在显式时使用 Assembly.Load 加载程序集。

Assembly.LoadFrom 的文档包含以下语句:“如果使用 LoadFrom 加载程序集,然后加载上下文中的程序集尝试按显示名称加载 [the] 相同的程序集,则加载尝试会失败。这可能会发生当程序集被反序列化时。”遗憾的是,没有提示为什么会发生这种情况。

在示例代码中,Assembly 没有被反序列化(而且我不完全确定反序列化 Assembly 意味着什么),但是正在反序列化一个委托;可以合理地假设反序列化委托涉及尝试“按显示名称”加载相同的程序集。

如果这是真的,那么如果委托指向的函数位于使用“LoadFrom 上下文”加载的程序集中的函数,则无法跨 AppDomain 边界传递委托。在这种情况下,使用CreateInstance 而不是CreateInstanceFrom 可以避免这个问题(因为CreateInstanceFrom 使用LoadFrom):

return (T)Activator.CreateInstance(newDomain,
    typeof(T).Assembly.FullName,
    typeof(T).FullName, false,
    0, null, constructorArgs, null, null).Unwrap();

但事实证明这是一条红鲱鱼;除非ApplicationBase 设置为包含我们的程序集的文件夹,否则CreateInstance 不能使用,并且如果ApplicationBase 设置为该文件夹,则订阅TestEvent 成功无论 CreateInstanceCreateInstanceFrom 用于在新的 AppDomain 中创建 T。因此,T 是通过LoadFrom 加载的事实本身并不会导致问题。

我尝试的另一件事是签署程序集并告诉 .NET 框架应该给予它完全信任:

newDomain = AppDomain.CreateDomain(appDomainName, null, setup, permSet,
    new StrongName[]  GetStrongName(typeof(T).Assembly) );

这依赖于来自 an MSDN article 的 GetStrongName 方法。不幸的是,这没有任何效果(即 FileNotFoundException 仍然发生)。

【讨论】:

以上是关于为啥我不能订阅部分信任 AppDomain 中的事件?的主要内容,如果未能解决你的问题,请参考以下文章

在部分受信任的 AppDomain 中调用 WCF 操作时出现“请求失败”

在中等信任的 ASP.Net 中读取程序集 Guid 而不锁定 appdomain 中的 DLL

.net 4 部分信任来自 GAC 的程序集

Lava是啥?为啥说Lava做的是有意义的事?

在沙盒 Appdomain 中加载程序集 - SecurityException

在新的 Appdomain 中加载程序集,需要完全信任父程序集