在.NET 4.0中,我如何“沙盒”内存中的程序集并执行一个方法?
这就是为什么要问这个问题的原因: www.devplusplus.com/Tests/CSharp/Hello_World 。
虽然以前也有类似的问题,但网上有很多答案有几个问题:
- 这必须做“.Net 4.0”风格,而不是传统模式。
- 程序集在内存中,只能在内存中, 不能写入文件系统。
- 我想限制所有访问文件系统,networking等
像这样的东西:
var evidence = new Evidence(); evidence.AddHostEvidence(new Zone(SecurityZone.Internet)); var permissionSet = SecurityManager.GetStandardSandbox(evidence);
到目前为止,我找不到一种方法来创build一个AppDomain,并加载一个不在文件系统上的程序集 ,而是在RAM中。
再次说明其他解决scheme无法正常工作的原因:1.很多是4.0以前版本,2.许多依赖于指向文件系统的“.Load”方法。
答案2:我有一个程序集引用,因为它是由CSharpCodeProvider
类生成的,所以如果你知道一种方法把它变成一个字节数组,那将是完美的!
示例代码显示安全缺陷
var provider = new CSharpCodeProvider(new Dictionary<String, String> { { "CompilerVersion", "v4.0" } }); var compilerparams = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = true, }; var compilerResults = provider.CompileAssemblyFromSource(compilerparams, string_Of_Code_From_A_User); var instanceOfSomeClass = compilerResults.CompiledAssembly .CreateInstance(className); // The 'DoSomething' method can write to the file system and I don't like that! instanceOfSomeClass.GetType().GetMethod("DoSomething") .Invoke(instanceOfSomeClass, null);
那么为什么我不能把程序集保存到一个文件呢?
有两个原因:
- 此代码位于共享Web服务器上,对文件系统本身的权限有限。
- 这个代码可能需要运行数千次,我不想要1000个dll,甚至是暂时的。
好的,首先要做的是:没有实际的方法来使用CSharpCodeProvider完全在内存中dynamic编译C#源代码。 有些方法似乎支持这个function,但是由于C#编译器是一个本地可执行文件,不能在进程中运行,所以源string被保存到一个临时文件,编译器被调用到该文件上,然后生成的程序集保存到磁盘,然后使用Assembly.Load加载。
其次,正如你发现的那样,你应该可以使用AppDomain中的Compile方法来加载程序集并给它所需的权限。 我遇到了同样的不寻常的行为,经过大量挖掘发现,这是一个框架中的错误。 我在MS Connect上提交了一份问题报告。
由于框架已经写入文件系统,解决方法是将程序集写入临时文件,然后根据需要加载。 但是,当你加载它时,你需要临时声明AppDomain的权限,因为你不允许访问文件系统。 这是一个例子的片段:
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert(); var assembly = Assembly.LoadFile(assemblyPath); CodeAccessPermission.RevertAssert();
从那里你可以使用汇编和reflection来调用你的方法。 请注意,这种方法可以让您在沙盒AppDomain之外提升编译过程,这在我看来是一个优点。
作为参考,这里是我创build的Sandbox类,以方便在具有有限权限的漂亮干净的单独AppDomain中启动脚本程序集,并且可以在必要时轻松卸载:
class Sandbox : MarshalByRefObject { const string BaseDirectory = "Untrusted"; const string DomainName = "Sandbox"; public Sandbox() { } public static Sandbox Create() { var setup = new AppDomainSetup() { ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory), ApplicationName = DomainName, DisallowBindingRedirects = true, DisallowCodeDownload = true, DisallowPublisherPolicy = true }; var permissions = new PermissionSet(PermissionState.None); permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess)); permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions, typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>()); return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap(); } public string Execute(string assemblyPath, string scriptType, string method, params object[] parameters) { new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert(); var assembly = Assembly.LoadFile(assemblyPath); CodeAccessPermission.RevertAssert(); Type type = assembly.GetType(scriptType); if (type == null) return null; var instance = Activator.CreateInstance(type); return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters)); } }
快速提示:如果您使用此方法为新的AppDomain提供安全证据,则需要在程序集上签名以赋予其强名称。
请注意,这在运行过程中可以正常工作,但是如果您确实需要防弹脚本环境,则需要更进一步,并在单独的进程中隔离该脚本,以确保脚本具有恶意(或者仅仅是愚蠢的)像堆栈溢出,叉炸弹和内存不足的情况不会导致整个应用程序的进程。 如果你需要的话,我可以给你更多的信息。