在沙盒AppDomain中运行运行时编译的C#脚本

在沙盒AppDomain中运行运行时编译的C#脚本,c#,appdomain,C#,Appdomain,我的应用程序应该可以由C#中的用户编写脚本,但用户的脚本应该在受限的AppDomain中运行,以防止脚本意外造成损坏,但我无法真正让它工作,而且由于我对AppDomain的理解非常有限,我真的无法解释原因 我目前尝试的解决方案就是基于这个答案 这是我的情况模型(除了驻留在强名称程序集中的Script.cs之外的所有内容)。请原谅代码墙,我无法进一步浓缩这个问题 class Program { static void Main(string[] args) { //

我的应用程序应该可以由C#中的用户编写脚本,但用户的脚本应该在受限的AppDomain中运行,以防止脚本意外造成损坏,但我无法真正让它工作,而且由于我对AppDomain的理解非常有限,我真的无法解释原因

我目前尝试的解决方案就是基于这个答案

这是我的情况模型(除了驻留在强名称程序集中的Script.cs之外的所有内容)。请原谅代码墙,我无法进一步浓缩这个问题

class Program
{
    static void Main(string[] args)
    {
        // Compile the script
        CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
        CompilerParameters parameters = new CompilerParameters()
        {
            GenerateExecutable = false,
            OutputAssembly = System.IO.Path.GetTempFileName() + ".dll",                         
        };
        parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);

        CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, "Script.cs");

        // ... here error checks happen ....//                 

        var sandbox = Sandbox.Create();
        var script = (IExecutable)sandbox.CreateInstance(results.PathToAssembly, "Script");

        if(script != null)
            script.Execute();

    }        
}  

public interface IExecutable
{
    void Execute();
}
沙盒类:

public class Sandbox : MarshalByRefObject
{
    const string BaseDirectory = "Untrusted";
    const string DomainName = "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 object CreateInstance(string assemblyPath, string typeName)
    {
        new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
        var assembly = Assembly.LoadFile(assemblyPath);
        CodeAccessPermission.RevertAssert();

        Type type = assembly.GetType(typeName); // ****** I get null here
        if (type == null)
            return null;

        return Activator.CreateInstance(type);            
    }
}
SandBox
CreateInstance
中,我总是在标记行处得到
null
。我尝试了各种形式的命名,包括使用反射从
results.CompiledAssembly
读取类型名(或完全限定名)。
我在这里做错了什么?

我要检查的第一件事是是否有编译错误(这个问题引起了我的一些头痛)

第二个想法是关于程序集的解析。我总是为AppDomain.CurrentDomain.AssemblyResolve添加一个事件处理程序作为安全检查,在其中,我在已知路径上查找缺少的程序集。当未找到的程序集是我刚刚编译的程序集时,我向它添加一个静态引用并返回它

我通常做的是:

  • 使用编译器在文件系统上创建新程序集
  • 使用文件.ReadAllBytes加载其内容
  • 加载带有程序集的dll。加载我将在其中使用该对象的AppDomain
  • 添加AppDomain.CurrentDomain.AssemblyResolve事件
以防万一(因为我经常使用它),我创建了一个小的库来容纳这种东西

代码和文档如下所示:
当nuget软件包在这里时:

我要检查的第一件事是是否有编译错误(我有几个头疼的问题是由这个问题引起的)

第二个想法是关于程序集的解析。我总是为AppDomain.CurrentDomain.AssemblyResolve添加一个事件处理程序作为安全检查,在其中,我在已知路径上查找缺少的程序集。当未找到的程序集是我刚刚编译的程序集时,我向它添加一个静态引用并返回它

我通常做的是:

  • 使用编译器在文件系统上创建新程序集
  • 使用文件.ReadAllBytes加载其内容
  • 加载带有程序集的dll。加载我将在其中使用该对象的AppDomain
  • 添加AppDomain.CurrentDomain.AssemblyResolve事件
以防万一(因为我经常使用它),我创建了一个小的库来容纳这种东西

代码和文档如下所示:
当nuget包在这里时:

如果我没有弄错的话,当您使用CodeDom编译程序集时,它会被加载到当前的AppDomain中。您应该在子AppDomain中编译,并拥有实现IExecutable的MarshallByRefObject派生类。这可以存储在应用程序和动态编译的程序集中引用的另一个程序集中。然后将其用作脚本的基类。这样就不会泄露任何类型。顺便说一下,脚本类需要是MarshalByRefObject派生的或可序列化的,才能跨AppDomains。感谢您的输入。我的印象是,只有设置了GenerateInMemory或使用了CompilerResults.CompiledAssembly属性,它才会加载到当前的AppDomain中。需要更多的研究。=)在新AppDomain内编译会在CodeDomProvider.CreateProvider上引发SecurityException,除非我使用PermissionState.Unrestricted,这是不受欢迎的。我还没有发现真正需要什么权限。祝你在权限搜索中好运。头痛。顺便说一句,您可以在具有完全权限的子AppDomain上编译,然后在另一个AppDomain中准备沙盒。如果我没有弄错的话,当您使用CodeDom编译程序集时,它将加载到当前AppDomain中。您应该在子AppDomain中编译,并拥有实现IExecutable的MarshallByRefObject派生类。这可以存储在应用程序和动态编译的程序集中引用的另一个程序集中。然后将其用作脚本的基类。这样就不会泄露任何类型。顺便说一下,脚本类需要是MarshalByRefObject派生的或可序列化的,才能跨AppDomains。感谢您的输入。我的印象是,只有设置了GenerateInMemory或使用了CompilerResults.CompiledAssembly属性,它才会加载到当前的AppDomain中。需要更多的研究。=)在新AppDomain内编译会在CodeDomProvider.CreateProvider上引发SecurityException,除非我使用PermissionState.Unrestricted,这是不受欢迎的。我还没有发现真正需要什么权限。祝你在权限搜索中好运。头痛。顺便说一下,您可以在具有完全权限的子AppDomain上编译,然后在另一个AppDomain中准备沙盒。
using System;

public class Script : IExecutable
{
    public void Execute()
    {
        Console.WriteLine("Boo");
    }
}