Запуск скомпилированного сценария C# в изолированной программной среде AppDomain

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

Решение, которое я сейчас пробую, основано на этом ответе /questions/32196985/vnet-40-kak-ya-mogu-pesochnitsej-sborki-v-pamyati-i-vyipolnit-metod/32196997#32196997.

Это модель моей ситуации (все, кроме 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 используя отражение. Что я здесь не так делаю?

1 ответ

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

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

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

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

На всякий случай (так как я часто этим пользуюсь) я создал небольшую библиотеку для выполнения подобных задач

Код и документация здесь: Kendar Expression Builder Пока пакет nuget находится здесь: Шаблон Nuget Sharp

Другие вопросы по тегам