9

我有一个使用脚本引擎编译脚本的代码片段,我将程序集作为字节数组检索。

现在我想将它加载到Assembly沙盒中,这就是我所拥有的:

Assembly _dynamicAssembly;
ScriptEngine _engine;
Session _session;

public string Execute(string code)
{
    // Setup sandbox
    var e = new Evidence();
    e.AddHostEvidence(new Zone(SecurityZone.Internet));
    var ps = SecurityManager.GetStandardSandbox(e);
    var setup = new AppDomainSetup 
                         { ApplicationBase = Environment.CurrentDirectory };
    var domain = 
        AppDomain.CreateDomain("Sandbox", 
                               AppDomain.CurrentDomain.Evidence, setup, ps);
    AppDomain.CurrentDomain.AssemblyResolve += DomainAssemblyResolve;

    // Process code
    var submission = _engine.CompileSubmission<object>(code, _session);
    submission.Compilation.Emit(memoryStream);
    var assembly = memoryStream.ToArray();

    _dynamicAssembly = Assembly.Load(assembly);

    var loaded = domain.Load(assembly);

    // Rest of the code...
}

这是 AssemblyResolve 的事件处理程序:

Assembly DomainAssemblyResolve(object sender, ResolveEventArgs args)
{
    return _dynamicAssembly;
}

这意味着当我这样做时,domain.Load(assembly)我将获得 _dynamicAssembly,如果我不订阅该事件并返回该事件Assembly,我将获得一个FileNotFoundException.

以上编译运行,但问题是在域程序集中执行的代码实际上并没有在沙箱中执行。当我得到提交方法并在其中调用工厂并返回AppDomain.CurrentDomain.FriendlyName结果是:MyRoslynApplication.vshost.exe不是沙箱AppDomain

我加载我的byte[]-assembly 错误吗?

4

2 回答 2

11

如果要加载在沙箱中运行的类型并从主 AppDomain 访问它,则需要使用CreateInstanceFromAndUnwrap之类的方法。该类型需要是 MarshalByRefObject 以便它可以在调用 AppDomain 中创建透明代理以进行访问。

如果主 AppDomain 解析程序集,它将被加载到主 AppDomain(以及沙盒 AppDomain)中,以便最终加载两个副本。您的主 AppDomain 必须始终通过代理与沙箱保持隔离,以便只能访问 MarshalByRefObject 和可序列化对象。请注意,您引用的类型也不能在您要加载到沙箱中的程序集中定义;您需要在第三个公共程序集中定义接口和可能的可序列化类型,然后将其加载到主 AppDomain 和沙盒 AppDomain 中。


我做了一些额外的挖掘,看起来所有将程序集加载到另一个 AppDomain 并生成代理的方法都需要一个程序集名称来解析。我不确定在这种情况下是否可以通过 byte[] 加载;您可能需要将程序集保存到磁盘并加载它。我再挖一点。


我认为你可以做到这一点(这是未经测试的,但似乎是合理的)。

这些需要位于主应用程序和沙箱都可以访问的“接口”程序集中(我将其称为 Services.dll):

public interface IMyService
{
    //.... service-specific methods you'll be using
}

public interface IStubLoader
{
    Object CreateInstanceFromAndUnwrap(byte[] assemblyBytes, string typeName);
}

接下来是 StubLoader.dll 中的一个类。您不会直接引用此程序集;这是您将调用第一个 AppDomain.CreateInstanceFromAndUnwrap 的地方,提供 this 作为程序集名称和 StubLoader 作为类型名称。

public sealed class StubLoader: MarshalByRefObject, IStubLoader
    {
        public object CreateInstanceFromAndUnwrap(byte[] assemblyBytes, string typeName)
        {
            var assembly = Assembly.Load(assemblyBytes);
            return assembly.CreateInstance(typeName);
        }
    }

现在,要从主 AppDomain 中使用它,请执行以下操作:

//Create transparent proxy for the stub loader, which will live in the sandbox
var stubLoader = (IStubLoader)sandboxDomain.CreateInstanceFromAndUnwrap("Stubloader.dll", "StubLoader");

//Have the stub loader marshal a proxy to a dynamically loaded assembly (via byte[]) where MyService is the type name implementing MarshalByRefObject and IMyService
var myService = (IMyService)stubLoader.CreateInstanceFromAndUnwrap(assemblyBytes, "MyService");

不幸的是,AppDomain 使用起来并不简单。这是因为它们提供了高度的隔离,因此需要代理以允许跨 AppDomain 边界使用。


作为对如何编组不可序列化和非 MarshalByRefObject 类的回应,这里是共享接口 DLL 中可能包含的内容的粗略示例:

public interface ISessionWrapper
{
    void DoSomethingWithSession();
}

public sealed class SessionWrapper : MarshalByRefObject, ISessionWrapper
{
    private readonly Session _session;

    public SessionWrapper(Session session)
    {
        _session = session;
    }

    public void DoSomethingWithSession()
    {
        //Do something with the wrapped session...
        //This executes inside the sandbox, even though it can be called (via proxy) from outside the sandbox
    }
}

现在,在您的原始服务需要使用 Session 的任何地方,它都可以传递 ISessionWrapper,其调用将在幕后编组,以便所有实际代码在沙箱中的沙箱中的真实 Session 实例上执行。

于 2011-11-29T15:15:20.470 回答
0

可能这也有帮助:

https://docs.microsoft.com/en-us/dotnet/framework/misc/how-to-run-partially-trusted-code-in-a-sandbox

此外,我必须将创建的程序集文件流保存到磁盘上以使沙箱正常工作。我仍在努力解决解决方案的强命名和单元测试(有超过 16 个项目),但是,我会用工作副本回复你。

于 2017-06-13T05:45:03.350 回答