How to Host a Partial Trust Sandbox – #7

In previous tips, I referenced some APIs that allowed me to run code in partial trust, and we’ll finally cover that code today, as well as some API changes made in .NET 4 to make it easier to set up the sandbox.

Where We’ve Come From

In .NET 1.1 and below, the only way to control trust levels was through CAS Policy, which was a powerful but very complex system for managing which permissions apply to given assemblies loaded in your application. The gist is that there are multiple levels of policy—Enterprise, Machine, User, and AppDomain—each with code groups and membership conditions for those code groups. Each code group specified a permission set, and the membership conditions specified which assemblies were classified in a given code group, based on its evidence, like its Zone, StrongName, Url, etc. Since an assembly can belong to multiple code groups, the permissions for an assembly were unioned across all code groups within a policy level, and then intersected across policy levels. But wait, there’s more! You can specify any policy level to be a "final" level or an "exclusive" level, which affects how the permissions are intersected…

If you’re feeling confused, then don’t worry. You’re not alone. If you want a more thorough discussion of CAS Policy, you can Google it. With CAS Policy’s deprecation and the subsequent focus on hosts to provide permissions instead of policy, the focus of this post is on the host, not CAS policy.

Where We Are

In .NET 2.0, the CLR team introduced new APIs which allow code to create a partial trust sandbox, where only the permissions that the host requests are granted to the code running within the sandbox. These sandboxes are actually homogeneous AppDomains, where every piece of code running in the assembly is subject to one of two permission grant sets:

  1. Full Trust
  2. The grant set of the AppDomain.

Assemblies will be full trust if they either (1) are loaded from the GAC or (2) appear on the AppDomain’s list of trusted assemblies. Here’s the method behind the sandboxing magic.

public static AppDomain CreateDomain(

    string friendlyName,

    Evidence securityInfo,

    AppDomainSetup info,

    PermissionSet grantSet,

    params StrongName[] fullTrustAssemblies

)

The interesting parameters are the PermissionSet and the array of StrongName instances that are considered full trust in the sandbox. The CLR will enforce that the sandbox has only the permissions of the PermissionSet passed to this method. The set of StrongNames that you can supply describes assemblies which the AppDomain will treat as full trust. You may wonder what it means to be a full trust assembly when demands for permissions traverse the entire call stack in an AppDomain; essentially, full trust assemblies are allowed to elevate their permissions using asserts and they can satisfy LinkDemands for permissions you don’t normally have in the AppDomain.

Let’s look at an example use of the AppDomain.CreateDomain sandbox method.

static void RunInPartialTrust()

{

    AppDomainSetup setup = new AppDomainSetup

    {

        ApplicationBase = Environment.CurrentDirectory

    };

 

    PermissionSet permissions = new PermissionSet(null);

    permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

    permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));

    AppDomain appDomain = AppDomain.CreateDomain(

        "Partial Trust AppDomain",

        null,

        setup,

        permissions

    );

 

    Program p = (Program)appDomain.CreateInstanceAndUnwrap(

        typeof(Program).Assembly.FullName,

        typeof(Program).FullName

    );

 

    p.PartialTrustMain();

}

The setup process is simple. Creating the PermissionSet requires a few lines of code where you explicitly supply which permissions you want for the sandbox, and creating the AppDomainSetup object is also trivial. From there, create the AppDomain with the CreateDomain method, instantiate a new object in that AppDomain, and call a method on it. As soon as you call that method, your code will transition from the default AppDomain to the new sandboxed domain. Note that the class you instantiate should inherit from ‘>MarshalByRefObject in order for it to be marshalled across AppDomain boundaries. (In the example above, the Program class inherits from MarshalByRefObject.)

You may wonder why I pass null for the Evidence parameter. Most APIs in .NET 4 that expose an Evidence parameter are deprecated because those methods typically interact with CAS policy to achieve their objectives. However, passing null is the same as passing the Evidence of the current (full-trust) AppDomain, which means it will not affect the sandbox. In fact, based on what I see in Reflector, if you pass in custom evidence, it will be ignored.

The more interesting use of the sandbox API arises when you need full trust assemblies in your new sandbox, but they don’t live in the GAC. Here you can use an improved version of the Evidence API exposed in .NET 4 to retrieve the StrongName instance from a given assembly. (Yes, that’s right. In order to be a full trust assembly in a sandbox, the assembly must be strong named.)

StrongName foo = typeof(Foo).Assembly.Evidence.GetHostEvidence<StrongName>();

 

If this were .NET 3.5, you would have to do this, so I’m sure you can appreciate the brevity of the new API.

StrongName sn;

IEnumerator enumerator = typeof(Foo).Assembly.Evidence.GetHostEnumerator();

while (enumerator.MoveNext())

{

    sn = enumerator.Current as StrongName;

    if (sn != null)

    {

        break;

    }

}

 

After you aggregate all of the StrongName instances that you need, pass them as the last parameter of AppDomain.CreateDomain to treat the assemblies identified by those StrongNames as full trust. Afterwards your sandbox is up and running, and you can start playing with partially trusted code.

3 thoughts on “How to Host a Partial Trust Sandbox – #7

  1. Pingback: Tip #20 – Opting Out of Security Changes in .NET 4 in ASP.NET and Custom AppDomains | David DeWinter

  2. Thanks for this insight. It has helped me to create a 90% solution to my problem, but I am struggling with the last 10% …

    I am using MAF to interface my WPF app to its 3rd party add-ins. MAF provides me with out-of-the-box functionality to run these add-ins in their own AppDomain and to specify the permissions assigned to that domain, but I also need to be able to run some of my own code in full-trust in the add-in domain, so I don’t think I can use this basic functionality?

    Instead, I am trying to use the MAF activation overload that accepts an AppDomain. Using the technique described in your post, I created an AppDomain that has restricted permissions, but is also given a list of my strong-named trusted assemblies. Unfortunately, when MAF executes AddInToken.Activate() it throws a SecurityException whilst performing the reflection necesary to discover the add-ins.

    Since the MAF assemblies are in the GAC, shouldn’t they automatically be fully trusted? Can you suggest anything else I could try to get past this obstacle.

    Thanks,
    Tim

    • The MAF assemblies are fully trusted, but remember that when demanding permissions the CLR checks the entire call stack for those permissions even across AppDomain boundaries, until it either reaches the end or hits an assert for the permission being demanded. I don’t have much experience with MAF but if you could provide a stack trace and message that could help further diagnose the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>