In .Net 4.0, How to 'Sandbox' an In-Memory Assembly and Execute a Method

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

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.

C# Load Sandboxed Assembly

Yes, this is possible. Using Code Access Security and the .NET Sandbox. I would advise you take a look at the CSScript library (open source). This is a library that provides C# scripting and on the fly compilation into dynamic assemblies. I have used this in a project to allow end-users to write C# scripts and execute them allowing interaction with classes in my system. The project required that they had no access to File IO or MessageBox (UI) as user scripts were to execute on a server. CSSCript used elements of the .NET framework to to limit what the assembly has access to and you will get an exception if any of these prohibited types are called.

So, take a look at that. I will edit my answer once I find out some more detail on how its possible, just to let you know it is possible!


Ok found it. Here is a discussion I had with the author of CSScript a few years ago:

Me:

I am developing an application and wish to expose the ability for users to script certain actions through the UI. CSScript looks very good for this. However I also wish to allow users to do this and execute their scripts on a web server. Now this is a security nightmare as users could write "Directory.Delete(@"C:\", true)" and wipe the hard drive. So would it be possible to restrict the assemblies, namespaces or even classes that a user can access from their script, to sort of run the CSScript in a secure sandbox?

Oleg:

The immediate attractive solution is to use .NET Sandbox. It is designed exactly for this sort of scenarios.The CLR standard sandboxing is available for the host application running scripts with CS-Script. The idea is that you initialise CAS before loading the suspicious script and the rest is a CLR responsibility. And if you need to configure directories/files permissions you do it with CAS tools. This way scripting is a "transportation" for the routine provided by your user. And CS-Script is a convenient mechanism for implementing such transport but the actual security concerns are addressed by .NET Sendoxing, which has comprehensive set of functionality to cover practically all possible security scenarios. With CS-script downloadables you can find the Sendboxing sample (\Samples\Sandboxing) which demonstrates how to prevent script from file I/O operations (e.g. create file).

So, from this I believe you need to look at .NET Sandbox and load the assemblies into that. I realise this example is specific to C# Scripting but I believe it is applicable to your scenario as CSScript examples above will show you a way to achieve sandboxing and security.

Some specific examples of how to load assemblies into a sandbox can be found here:

  • Loading .NET Assemblies in a Sandbox
  • Code Access Security in practice
  • How to use CAS to constrain an assembly

While we're discussing module loading, have you heard of Microsoft Prism? This provides a good framework for module loading and dependency injection (via Unity or MEF) which could be very useful when developing a plugin architecture.

Best regards,

Compiled in memory Assembly object to bytes[]

This is not possible in the runtime.

The only possible solution is to use WinDbg and SOS to dump the assembly. But then again, all my attempts at doing that exact same thing has failed.

Attach in-memory assembly in custom AppDomain

You get your new AppDomain to do it. You have to instantiate a class (that inherits MarshalByRefObject) that will run in the chosen AppDomain and load the assembly for you. This involves .NET remoting. Tell me more in this MSDN article. Though it mentions partially trusted code, great sections still apply.

It's vitally important to have the class inheriting MarshalByRefObject running in the sandbox domain to load the assembly in case the code is not trust-worthy.

Proxy/Sandbox/Helper Class

This class acts as an intermediary between your code running in the primary AppDomain (where you created the child domain) and any code that you wish to run in the sandbox/child domain. When using child domains, objects in the primary domain should only talk directly to the proxy class that is running in the child domain for security reasons.

Communication is by .NET Remoting behind the scenes - not that you would know it. Hence why the class is MarshalByRefObject.

e.g. (modified MSDN example)

class Sandboxer:MarshalByRefObject
{
public void LoadDodgyAssembly(string path) { ... }
}

Host Code

Use this code where you create the child AppDomain. The following code should run in the primary AppDomain:

var appDomain = CreateSandBox();

var handle = Activator.CreateInstanceFrom(
appDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName );

var sandboxer = (Sandboxer) handle.Unwrap();
sandboxer.LoadDodgyAssembly("pleaserunme.dll");

Return Values

Be careful of what your proxy class returns to your calling code in the primary AppDomain. If you don't trust the loaded assembly might be best to mark your methods as void otherwise consider using your own trustworthy types marked as [Serializable].

Tell me more

  • How to: Run Partially Trusted Code in a Sandbox

There was a magnificent article in MSDN Magazine circa ~2005 entitled "Add-ins: Do you trust it" or something similar but am unable to find.

Looking for a practical approach to sandboxing .NET plugins

Because you're in different AppDomains, you can't just pass the instance across.

You'll need to make your plug-ins Remotable, and create a proxy in your main app. Have a look at the docs for CreateInstanceAndUnWrap, which has an example of how all this could work towards the bottom.

This is also another much broader overview by Jon Shemitz which I think is a good read. Good luck.

C# or C++ sandboxed assembly

If you restrict the subset of supported instructions, you can do what you want more or less easily.

First, you have to parse and decode an input instruction to see if it's in the supported subset (most of parsing/decoding can be done just once). Then you need to execute it.

But before executing, there's one important thing to take care of. Based on the decoded details of the instruction and the CPU registers state, you have to calculate the memory addresses that the instruction is going to access as data (including on-stack locations) or transfer control to. If any of those are outside of the established limits, fire alarm. Otherwise, if it's a control transferring instruction (e.g. jmp, jz), you must additionally ensure that the address it passes control to is not only within the memory, where all these instructions lie, but also is the address of one of those instructions and not an address inside of any of them (e.g. 1 or 2 bytes from the beginning of a 3+ bytes long instruction). Passing control anywhere else is a no-no. You do not want these instructions to pass control to any standard library functions either because you won't be able to control execution there and they're not always safe when supplied with bogus/malicious inputs. Also, these instructions must not be able to modify themselves.

If all is clear, you can either emulate the instruction or more or less directly execute it (control passing instructions will likely have to be always emulated because you want to stop execution after every instruction). For the latter you can create a modifiable function containing these things:

  1. Code to save CPU registers of the caller and load them with the state for the instruction being executed.
  2. The instruction.
  3. The reverse of step 1: code to save post-execution register state and restore the caller's register state.

You can try this approach.



Related Topics



Leave a reply



Submit