В .NET 4.0, как я могу «песочницей» сборки в памяти и выполнить метод? - PullRequest
47 голосов
/ 14 мая 2011

Вот причина, по которой этот вопрос задавался: www.devplusplus.com / Tests / CSharp / Hello_World .

Хотя подобные вопросы задавались ранее, многие ответы в Интернете имеютнесколько проблем:

  1. Это должно быть сделано в стиле ".Net 4.0", а не в устаревшем режиме.
  2. Сборка находится в памяти и будет только в памяти, это невозможно записать в файловую систему.
  3. Я хотел бы ограничить весь доступ к файловой системе, сети и т. д.

Примерно так:

    var evidence = new Evidence();
    evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
    var permissionSet = SecurityManager.GetStandardSandbox(evidence);

Пока что я не могу найти способ создать домен приложения и загрузить сборку ЭТО НЕ В ФАЙЛОВОЙ СИСТЕМЕ , а скорее в ОЗУ.

Опять же,Причины, по которым другие решения не работали, указаны выше: 1. Многие из них были до версии 4.0, и 2. Многие полагались на метод «.Load», указывающий на файловую систему.

Ответ 2: Iиметь ссылку на сборку, поскольку она генерируется классом 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);

Так почему я не могу просто сохранить сборку в файлfirst?

По двум причинам:

  1. Этот код находится на общем веб-сервере с ограниченными разрешениями на саму файловую систему.
  2. Этот код может потребоватьсябыть запущенным потенциально тысячи раз, и я не хочу 1000 dll, даже временно.

1 Ответ

42 голосов
/ 14 мая 2011

ОК, обо всем по порядку: нет никакого реального способа использовать CSharpCodeProvider для динамической компиляции исходного кода C # полностью в памяти. Существуют методы, которые поддерживают эту функциональность, но поскольку компилятор C # является собственным исполняемым файлом, который не может быть запущен внутри процесса, исходная строка сохраняется во временном файле, компилятор вызывается для этого файла, а затем результирующая сборка сохраняется на диск и затем загружается для вас с помощью Assembly.Load.

Во-вторых, как вы обнаружили, вы должны иметь возможность использовать метод Compile из AppDomain, чтобы загрузить сборку и дать ей необходимые разрешения. Я столкнулся с тем же самым необычным поведением, и после многих копаний обнаружил, что это ошибка в фреймворке. Я отправил отчет о проблеме на MS Connect .

Так как фреймворк уже записывает в файловую систему, обходной путь - записать сборку во временный файл и затем загрузить при необходимости. Однако, когда вы загрузите его, вам нужно будет временно утвердить разрешения в AppDomain, поскольку вы запретили доступ к файловой системе. Вот пример этого фрагмента:

new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();

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

Для справки, вот мой класс 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));
    }
}

Краткое примечание: если вы используете этот метод для предоставления доказательств безопасности для нового домена приложений, вам необходимо подписать сборку, чтобы дать ей строгое имя.

Обратите внимание, что это прекрасно работает при запуске в процессе, но если вы действительно хотите использовать среду с пуленепробиваемыми сценариями, вам нужно сделать еще один шаг и изолировать сценарий в отдельном процессе, чтобы гарантировать, что сценарии являются вредоносными (или просто глупо) такие вещи, как переполнение стека, разветвление бомб и ситуации с нехваткой памяти, не разрушают весь процесс приложения. Я могу дать вам больше информации, если вам это нужно.

...