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

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

问题描述

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

class 域:MarshalByRefObject{公共事件 Action TestEvent;}Domain domain = AppDomainStarter.Start(@"C:\Temp", "Domain", null, true);domain.TestEvent += () =>{ };//安全异常

订阅失败并显示消息请求'System.Security.Permissions.ReflectionPermission, mscorlib, Version=4.0.0.0...' 类型的权限失败."

(有关 AppDomainStarter 的定义,请参阅我对另一个问题的回答.)>

请注意,ApplicationBase C:\TempNOT 包含包含域的程序集的文件夹.这是故意的;我的目标是在新的 AppDomain 中加载第二个第 3 方不受信任的程序集,第二个程序集位于 C:\Temp(或其他任何地方,甚至可能是网络共享).但是在我加载第二个程序集之前,我需要在新的 AppDomain 中加载我的 Domain 类.这样做成功了,但由于某种原因,我无法跨 AppDomain 边界订阅事件(我可以调用方法,但不能订阅事件).

UPDATE:显然,在沙盒 AppDomain 中订阅事件时,订阅者方法和包含订阅者的类都必须是公共的.例如:

公共静态类程序{类域:MarshalByRefObject{公共事件 Action TestEvent;public Domain() { Console.WriteLine("Domain created OK");}}静态无效主(){string loc = @"C:\Temp";Domain domain = AppDomainStarter.Start(loc, "Domain", null, true);//这次不同的例外!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));//同样的异常仍然发生

我可以使用 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"的程序集是从'file:///C:/Users/.../TestApp.exe' 使用 LoadFrom 上下文.使用这种上下文可能会导致序列化的意外行为,铸造和依赖解析.在几乎所有情况下,它是建议避免使用 LoadFrom 上下文.这可以通过在全局程序集缓存或ApplicationBase 目录并在显式时使用 Assembly.Load加载程序集.

Assembly.LoadFrom 的文档包含以下声明:如果使用 LoadFrom 加载程序集,然后加载上下文中的程序集尝试按显示名称加载 [同一个] 程序集,加载尝试失败.当程序集被反序列化时,可能会发生这种情况."遗憾的是,没有任何关于为什么会发生这种情况的提示.

在示例代码中,一个 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();

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

我尝试的另一件事是对程序集进行签名并告诉 .NET Framework 应该完全信任它:

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

这依赖于 一篇 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天全站免登陆