Как в .NET 4.0 «изолировать» сборку в памяти и выполнить метод?

Вот причина, по которой был задан этот вопрос: 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: У меня есть ссылка на сборку, поскольку она создается классом 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);

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

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

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

person Timothy Khouri    schedule 13.05.2011    source источник
comment
Можете ли вы маршалировать байтовый массив в этот другой домен приложения и загрузить из него сборку? Также обратите внимание: если вы говорите, что другие решения не работают, поясните, почему они не работают. Это как бы подразумевает, что другие решения требуют, чтобы сборка была записана на диск. Вы уверены, что все другие решения имеют это требование?   -  person Lasse V. Karlsen    schedule 14.05.2011
comment
Как в хочешь использовать Load с байтовым массивом? Или вы имеете в виду, что создаете в памяти, используя API emit?   -  person tyranid    schedule 14.05.2011
comment
@tyranid Это очень хороший вопрос, я полагаю, я вроде как предполагал, что сборка загружается из чего-то, что можно преобразовать в массив байтов. Если он генерируется динамически, код, генерирующий сборку, может потребоваться запустить в этом другом домене приложения.   -  person Lasse V. Karlsen    schedule 14.05.2011
comment
@Lasse и @tyranid - ответы на ваши очень хорошие вопросы были добавлены к исходному вопросу.   -  person Timothy Khouri    schedule 14.05.2011
comment
Я думаю, вам нужно запустить код с CSharpCodeProvider в этом другом домене приложения. Решило бы это вашу проблему? (хотя может появиться множество новых)   -  person Lasse V. Karlsen    schedule 14.05.2011
comment
Можете ли вы опубликовать фрагмент, который также генерирует сборку?   -  person Can Gencer    schedule 14.05.2011
comment
Для меня это звучит как возможная проблема безопасности (по сути, выполнение данных / записываемой памяти). Нет другого способа сделать это? Каковы ваши намерения?   -  person Mario    schedule 14.05.2011
comment
Раньше я работал над этой проблемой и не мог найти способ получить байт COFF [] без записи сборки на диск. Если у вас есть проблемы с доступом, вы можете использовать изолированное хранилище. В качестве альтернативы, может быть какой-то способ настроить виртуальный путь к файлу, который записывает в память, хотя я не уверен.   -  person Dan Bryant    schedule 14.05.2011
comment
@Dan, байт COFF [] - это именно то, что я ищу ... может, стоит отразить метод Save и написать свой?   -  person Timothy Khouri    schedule 14.05.2011
comment
Не могли бы вы сохранить сборку на диск, а затем загрузить ее в собственный домен приложения с ограниченными разрешениями? Сохранение сборки на диск не представляет проблемы с безопасностью, а загрузка ее без отметки - делает.   -  person Lasse V. Karlsen    schedule 14.05.2011
comment
@ Тимоти, почему бы вам не создать сборку во временном месте и не выполнить ее в песочнице? По какой причине вы хотите, чтобы он был именно в ОЗУ?   -  person Can Gencer    schedule 14.05.2011
comment
@Lasse и @Can - ответили в исходном вопросе.   -  person Timothy Khouri    schedule 14.05.2011
comment
Я думаю, вам придется подождать, пока C # 5.0 сможет компилировать в памяти. Прямо сейчас он просто выполняет csc.exe, что означает, что исходный код и двоичный файл попадают на жесткий диск.   -  person Gabe    schedule 14.05.2011


Ответы (1)


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

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

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

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

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

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

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));
    }
}

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

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

person MikeP    schedule 13.05.2011
comment
Хорошо, я все еще немного запутался ... если у меня есть доступ для утверждения разрешений файловой системы, разве у вызываемого мной класса экземпляра не такие права? - person Timothy Khouri; 14.05.2011
comment
Нет. Вы вызываете Execute () из своей основной сборки, которая, очевидно, имеет разрешение запускать все, что захочет. У загруженной сборки сценария ограничены права доступа, поэтому она не сможет подтвердить их сама. Легко попробовать на себе и убедиться. - person MikeP; 14.05.2011
comment
@MikeP - песочница в моем случае - это консольное приложение. Как мне его подписать? Я получаю сообщение об ошибке. Нулевое сильное имя .. - person Nick; 13.11.2011