Simple Sandboxing and the LoadFrom Demand

One of the common problems that people run into when setting up simple sandbox domains in their application is that any call through to Assembly.LoadFrom results in a FileIOPermission demand for read access to the assembly in question.  Generally, the simple sandbox domain won't be granted this permission, so when the demand hits the AppDomain it will fail.

If you're doing an explicit LoadFrom, it's easy enough to stick an Assert in your code and prevent the demand from hitting the AppDomain ... but oftentimes in setting up the sandbox the call is not directly in your code and the Assert solution won't work.  I've run into two general classes of this problem, via the CreateInstanceFrom and ExecuteAssembly APIs.

Once you've setup your sandboxed domain you'll generally want to run some initialization code in it before loading whatever sandboxed code you'll be using.  Since the AppBase of this domain should not be pointing at your application's install directory, you'll need to use AppDomain.CreateInstanceFrom rather than AppDomain.CreateInstance to create your initialization object in the sandboxed domain.  The problem is that when you do this, the call stack looks similar to:

sandboxedDomain.CreateInstanceFrom FullTrust
Sandboxed AppDomain Boundary Partial Trust
AppDomain.CreateInstanceFrom() FullTrust
Assembly.LoadFrom() FullTrust
FileIOPermission.Demand()

Since there's no way for you to inject yourself between the demand and the AppDomain boundary, the LoadFrom will always fail when it hits that boundary.

To solve this problem, you can instead use the Activator.CreateInstanceFrom overload which takes an AppDomain as its first parameter.  This overload will make the call stack look like this instead:

sandboxedDomain.CreateInstanceFrom FullTrust
Sandboxed AppDomain Boundary Partial Trust
new PermissionSet(PermissionState.Unrestricted).Assert() FullTrust
AppDomain.CreateInstanceFrom() FullTrust
Assembly.LoadFrom() FullTrust
FileIOPermission.Demand()

As you can see, there's now an Assert for FullTrust injected between the partial trust domain boundary and the call to CreateInstanceFrom, which prevents the FileIO demand from failing.

This also has the consequence that your class' constructor will run under an Assert for FullTrust, and should not rely on the AppDomain boundary to provide any protection.  Generally, you should keep this constructor as simple as possible for this very reason.  You certainly don't want to be calling out to any partial trust code or code not under your control from there.  Ideally, you should factor most of this type of work out to an Initialize method which can be called without the assert in place.

In addition to any explicit constructor code, you should look out for constructor code created for you by the compiler when you assign values to class members, and any static constructors that you may cause to run under the Assert.

The other case where the LoadFrom demand can bite you is much easier to fix.  If the code you're sandboxing is an executable, it's convenient to use the AppDomain.ExecuteAssembly method to run it in your sandbox.  However ExecuteAssembly is going to do a LoadFrom to get the assembly loaded into the domain, and that triggers the infamous FileIO demand.  In this case, the fix is simply to use ExecuteAssemblyByName instead.

This API uses Assembly.Load to get the target assembly into the domain which means there will be no demand; however the AppBase must be setup to point at the target's location.  Since this is generally the way a sandboxed domain should be setup anyway, that's not too much of a problem.