为什么我不能在部分信任的AppDomain中订阅事件? [英] Why can't I subscribe to an event in a partial-trust AppDomain?

查看:42
本文介绍了为什么我不能在部分信任的AppDomain中订阅事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的默认(完全信任)AppDomain中,我想创建一个沙箱AppDomain并订阅其中的事件:

  class域:MarshalByRefObject{公共事件Action TestEvent;}域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 NOT 所在的文件夹,该文件夹包含包含Domain的程序集.这是故意的;我的目标是在新的AppDomain中加载第二个第三方不受信任的程序集,而该第二个程序集位于C:\ Temp(或其他任何位置,甚至可能是网络共享)中.但是在加载第二个程序集之前,我需要在新的AppDomain中加载我的 Domain 类.这样做可以成功,但是由于某些原因,我无法跨AppDomain边界订阅事件(我可以调用方法,但不能订阅事件).

更新:很明显,在沙箱AppDomain中订阅事件时,订户方法和包含订户的类都必须是公共的.例如:

 公共静态类Program{类域:MarshalByRefObject{公共事件Action TestEvent;public Domain(){Console.WriteLine("Domain created OK");}}静态void Main(){字符串loc = @"C:\ Temp";域domain = AppDomainStarter.Start< Domain>(loc,"Domain",null,true);//这次不同的例外!domain.TestEvent + = new Action(domain_TestEvent);}公共静态无效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(新FileIOPermission(FileIOPermissionAccess.Read,folderOfT));//仍然发生相同的异常 

我可以为 AppDomainSetup.ApplicationBase 使用不同的值来解决"问题:

  string loc = Path.GetFullPath(Assembly.GetExecutingAssembly().Location + @"\ .."); 

这消除了异常,但是我不能使用此解决方案",因为AppDomain的目的是从不同于包含我自己的程序集的文件夹的其他文件夹中加载不受信任的程序集.因此, loc 必须是包含不受信任程序集的文件夹,而不是包含我的程序集的文件夹.

解决方案

我能找到的唯一可行的方法是将程序集(其中包含要在新AppDomain中运行的代码)放入GAC.当然,这是一个巨大的麻烦,但这是唯一有效的方法.

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

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

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

Assembly.LoadFrom 的文档包括以下语句:程序集将使用LoadFrom加载,然后在加载上下文中,程序集尝试按显示名称加载同一程序集,则加载尝试将失败.当对程序集进行反序列化时,可能会发生这种情况."可悲的是,没有任何迹象表明为什么会发生.

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

如果这是正确的,则如果委托指向使用"LoadFrom上下文"加载的程序集中的函数,则不能跨AppDomain边界传递委托.在这种情况下,使用 CreateInstance 而不是 CreateInstanceFrom 可以避免此问题(因为 MSDN文章中的GetStrongName方法.不幸的是,这没有效果(即FileNotFoundException仍然发生).

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 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).

这篇关于为什么我不能在部分信任的AppDomain中订阅事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆