将Roslyn编译的程序集加载到沙箱AppDomain中

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

现在我想在Sandbox中加载这个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(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

上面编译并运行,但问题是在域程序集中执行的代码实际上并未在沙箱中执行。 当我获得submission-method并在其中调用工厂并返回此AppDomain.CurrentDomain.FriendlyName ,结果是: MyRoslynApplication.vshost.exe 不是沙箱AppDomain

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

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

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


我已经做了一些额外的挖掘,看起来所有将程序集加载到另一个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的地方,将其作为程序集名称和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"); 

不幸的是,AppDomains使用起来并不简单。 这是因为它们提供高度绝缘,因此需要代理以允许跨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,它的调用将在幕后进行编组,以便所有实际代码都在沙箱中生成的实际会话实例的沙箱中执行。

可能这也有帮助:

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

此外,我不得不将创建的汇编文件流保存到磁盘上以使沙盒工作。 我仍然在努力寻求解决方案的强命名和unit testing(有超过16个项目),但是,我会用工作副本回复你。