Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In .NET 4.0, how do I 'sandbox' an in-memory assembly and execute a method?

Here is the reason why this question was being asked: www.devplusplus.com/Tests/CSharp/Hello_World.

While similar questions were asked before, the many answers online have several issues:

  1. This must be done ".Net 4.0" style, not legacy mode.
  2. The assembly is in-memory and will only be in memory, it cannot be written to the file system.
  3. I would like to limit all access to the file-system, network, etc.

Something like this:

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

So far, I cannot find a way to create an AppDomain and load an assembly THAT IS NOT ON THE FILE SYSTEM, but rather in RAM.

Again, the reasons why the other solutions didn't work are identified above: 1. Many were for pre-4.0, and 2. Many relied on the ".Load" method pointing to the file system.

Answer 2: I have an assembly reference due to it being generated by the CSharpCodeProvider class, so if you know a way to turn that into a byte array, that would be perfect!

Sample Code to Show The Security Flaw

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

So why can't I just save the assembly to a file first?

For two reasons:

  1. This code is on a shared web server with limited permissions to the file-system itself.
  2. This code may need to be run potentially thousands of times, and I don't want 1,000 dlls, even temporarily.
like image 446
Timothy Khouri Avatar asked May 13 '11 21:05

Timothy Khouri


1 Answers

OK, first things first: there's no actual way to use the CSharpCodeProvider to do dynamic compilation of C# source entirely in memory. There are methods that seem to support that functionality, but since the C# compiler is a native executable that cannot run in-process, the source string is saved to a temporary file, the compiler is invoked on that file, and then the resulting assembly is saved to disk and then loaded for you using Assembly.Load.

Secondly, as you've discovered, you should be able to use the Compile method from within the AppDomain to load the assembly and give it the desired permissions. I ran into this same unusual behavior, and after a lot of digging found that it was a bug in the framework. I filed an issue report for it on MS Connect.

Since the framework is already writing to the filesystem anyway, the workaround is to have the assembly written to a temporary file and then loaded as needed. When you load it however, you'll need to temporarily assert permissions in the AppDomain, since you've disallowed access to the file system. Here's an example snippet of that:

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

From there you can use the assembly and reflection to invoke your method. Note that this method lets you hoist the compilation process outside of the sandboxed AppDomain, which is a plus in my opinion.

For reference, here is my Sandbox class created to facilitate the launching of script assemblies in a nice clean separate AppDomain that has limited permissions and can be easily unloaded when necessary:

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

Quick note: if you use this method to supply security evidence for the new AppDomain, you need to sign your assembly to give it a strong name.

Note that this works fine when run in process, but if you really want a bullet-proof script environment, you need to go one step further and isolate the script in a separate process to ensure that scripts that do malicious (or just stupid) things like stack overflows, fork bombs, and out of memory situations don't bring down the whole application process. I can give you more information on doing that if you need it.

like image 143
MikeP Avatar answered Oct 21 '22 05:10

MikeP