2013-08-15 2 views
1

Мое приложение должно быть доступно для сценариев для пользователей на C#, но скрипт пользователя должен работать в ограниченном AppDomain, чтобы предотвратить случайное причинение вреда сценариям, но я не могу получить его до работа, и поскольку мое понимание AppDomains, к сожалению, ограничено, я не могу сказать, почему.Запуск скомпилированного сценария C# в песочнице AppDomain

Решение, на котором я сейчас пытаюсь, основывается на этом ответе https://stackoverflow.com/a/5998886/276070.

Это модель моей ситуации (все, кроме 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);    
    } 
} 

Загруженный Сценарий:

using System; 

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

В CreateInstance из SandBox, я всегда получаю null на отмеченной линии. Я пробовал различные формы присвоения имени, включая чтение имени типа (или фамилии с наивысшим названием) от results.CompiledAssembly с использованием отражения. Что я здесь делаю неправильно?

+0

Если я Не ошибиться при компиляции сборки с помощью CodeDom, она загружается в текущий AppDomain. Вы должны скомпилировать в дочернем AppDomain и иметь производный класс MarshalByRefObject, который реализует IExecutable. Это можно сохранить в другой сборке, на которую ссылаются как приложение, так и динамически скомпилированная сборка. Затем используйте это как базовый класс для своих скриптов. Таким образом, никакой тип не просачивается. Кстати, класс Script должен либо быть выведенным или сериализуемым MarshalByRefObject, чтобы иметь возможность пересекать AppDomains. –

+0

Спасибо за ввод. У меня создалось впечатление, что он загрузился только в текущий AppDomain, если установлен GenerateInMemory или используется свойство CompilerResults.CompiledAssembly. Требуется больше исследований. =) Компиляция внутри нового AppDomain создает исключение SecurityException для CodeDomProvider.CreateProvider, если я не использую PermissionState.Неограниченный, что нежелательно. Я еще не выяснил, какие разрешения действительно нужны. – Jens

+0

Удачи вам в квете разрешений. Это головная боль. Кстати, вы можете скомпилировать дочерний AppDomain с полными разрешениями, а затем подготовить песочницу в другом. –

ответ

0

Первое, что я буду проверять, если есть ошибки компиляции (я имел несколько головной боль, вызванную этими вопросами)

Вторая идея о разрешении собраний. Я всегда добавляю в качестве проверки безопасности обработчик событий для AppDomain.CurrentDomain.AssemblyResolve, где я ищу свой известный путь для отсутствующих сборок. Когда не найденная сборка является той, которую я только что скомпилировал, я добавляю к ней статическую ссылку и возвращаю ее.

То, что я обычно делаю это:

  • Создать новую сборку в файловой системе с компилятором
  • Загрузите его содержимое с File.ReadAllBytes
  • Load DLL, с Assembly.Load в AppDomain, в котором я буду использовать объект
  • Добавить событие AppDomain.CurrentDomain.AssemblyResolve

Только в случае (так как я использую это много) я создал небольшую библиотеку accomply такого рода вещи

код и документация здесь: Kendar Expression Builder Хотя пакет NuGet здесь: Nuget Sharp Template

Смежные вопросы