Coding with Security Policy in .NET 4 part 2 – Explicit uses of CAS policy

Over the last few posts, I’ve been looking at how the update to the CLR v4 security policy interacts with how you write managed code against the v4 .NET Framework.  So far we’ve looked at the implicit uses of CAS policy, such as loading assemblies and creating AppDomains with Evidence and loading assemblies from remote sources.  Now let’s look at how to work with code which was written to work with CAS policy explicitly.

The good news is that explicit use of CAS policy is frequently very easy to spot, as opposed to implicit uses which can be somewhat more subtle.  APIs that directly manipulate policy (such as SecurityManager.ResolvePolicy) as well as those that require CAS policy to sandbox (such as AppDomain.SetAppDomainPolicy) fall into this category.  Other APIs that explicitly use CAS policy are:

  • AppDomain.SetAppDomainPolicy
  • HostSecurityManager.DomainPolicy
  • PolicyLevel.CreateAppDomainLevel
  • SecurityManager.LoadPolicyLevelFromString
  • SecurityManager.LoadPolicyLevelFromFile
  • SecurityManager.ResolvePolicy
  • SecurityManager.ResolveSystemPolicy
  • SecurityManager.ResolvePolicyGroups
  • SecurityManager.PolicyHierarchy
  • SecurityManager.SavePolicy

As with the implicit CAS policy uses, the explicit APIs also are obsolete in .NET 4, and will throw NotSupportedExceptions by default:

System.NotSupportedException: This method uses CAS policy, which has been obsoleted by the .NET Framework. In order to enable CAS policy for compatibility reasons, please use the NetFx40_LegacySecurityPolicy configuration switch. Please see https://go.microsoft.com/fwlink/?LinkId=131738 for more information.

Let’s take a look at how code which used these APIs in the past might get updated with new v4 APIs.

Generally, there are three reasons that the explicit policy APIs are being used:

  1. The code wants to figure out what the grant set of an assembly or AppDomain is
  2. The code wants to create a sandbox
  3. The code wants to figure out what a safe sandbox is to setup

The correct way way to update the code calling an explicit policy API in v4 depends upon what it was trying to do by calling the API in the first place.  Let’s take a look at each of the reasons for using an explicit policy API in turn and figure out what the replacement code should look like.

Figuring out what the grant set of an assembly or AppDomain is

Sometimes an application or library wants to figure out what the grant set of a particular assembly or domain was and would do so with code similar to:

 private PermissionSet GetAssemblyGrantSet(Assembly assembly){    Evidence assemblyEvidence = assembly.Evidence;    return SecurityManager.ResolvePolicy(assemblyEvidence);} private bool IsFullyTrusted(Assembly assembly){    PermissionSet grant = GetAssemblyGrantSet(assembly);    return grant.IsUnrestricted();} private PermissionSet GetAppDomainGrantSet(AppDomain domain){    Evidence appDomain = domain.Evidence;    return SecurityManager.ResolvePolicy(appDomain);} private bool IsFullyTrusted(AppDomain domain){    PermissionSet grant = GetAppDomainGrantSet(domain);    return grant.IsUnrestricted();}

This code worked by resolving the assembly or AppDomain’s evidence through CAS policy to determine what would be granted to that particular evidence.  There are a few problems here – for instance, the code doesn’t take into account simple sandbox domains, hosted AppDomains, dynamic assemblies, or assemblies loaded from byte arrays.  (Take a look at AssemblyExtensionMethods.GetPermissionSet() on https://clrsecurity.codeplex.com for code that does take most of the other considerations into account).   These methods also cause a full CAS policy resolution to occur, which is not a cheap operation. 

Instead of requiring people to manually jump through hoops in order to recreate the CLR’s security policy system in v4, we’ve directly exposed the grant sets of assemblies and AppDomains as properties of the objects themselves.  The above code can be replaced with:

 private PermissionSet GetAssemblyGrantSet(Assembly assembly){    return assembly.PermissionSet;} private bool IsFullyTrusted(Assembly assembly){    return assembly.IsFullyTrusted;} private PermissionSet GetAppDomainGrantSet(AppDomain domain){    return domain.PermissionSet;} private bool IsFullyTrusted(AppDomain domain){    return domain.IsFullyTrusted;}

Which has the dual benefit of being more accurate (these properties read the real grant set that the CLR is using, no matter how it was determined), and also being faster than a full policy resolution.

Accessing the PermissionSet property of an AppDomain or an Assembly does require that the accessing code be fully trusted.  The reason is that the permission sets themselves can contain sensitive data.  (For instance, FileIOPermission can contain full path information about the local machine in it).   Partial trust code, however, can use the IsFullyTrusted property.

Creating a Sandbox

I suspect many people who have read this blog already know what I’m going to say here :-)  Instead of using SetAppDomainPolicy to create a sandbox, which suffers from many problems, the replacement API is the simple sandboxing API.  I’ve already covered most of the reasoning for this change when I talked about sandboxing in CLR v4, so let’s look at the final reason that code may have been using CAS policy APIs

Figuring out what a safe grant set is to provide a sandbox

Sometimes a host needs to figure out what is a reasonable set of permissions to assign to a sandbox.  For instance, even though ClickOnce does not use CAS policy, it still needs to figure out if the permission set that the ClickOnce application is requesting is a reasonable set of permissions for it to have.   (For instance, if it’s requesting only the permission to execute, that’s going to be fine, while if an application from the Internet is requesting permission to read and write all of the files on your disk, that’s not such a good idea).

In order to solve this problem in v2, code might look like this:

 private bool IsSafeGrantSet(PermissionSet grantSet, Evidence sandboxEvidence){    // Figure out what the CLR's policy system says is safe to give a sandbox    // with this evidence    PermissionSet systemGrantSet = SecurityManager.ResolveSystemPolicy(sandboxEvidence);     // We'll consider this safe only if we're requesting a subset of the safe    // sandbox set.    return grantSet.IsSubsetOf(systemGrantSet);}

Since system wide CAS policy (which this code depends upon to determine safety) is deprecated in v4, we need to find a new way to accomplish this goal.

The answer is with a new API called GetStandardSandbox.   GetStandardSandbox is used to have the CLR provide what it considers a safe sandbox grant set for an AppDomain that will host code with the specified evidence.  It’s the CLR’s way of providing suggestions to hosts who are making trust decisions.   One thing that is very important to note is what GetStandardSandbox is not however.

GetStandardSandbox is not a policy API.  This isn’t the CLR applying CAS to evidence in order to modify grant set, and the CLR does not take any external factors such as CAS policy into account when returning its grant set.  Instead, GetStandardSandbox is simply a helper API for hosts which are trying to setup sandboxes.

With that in mind, the way the above code would be written in CLR v4 is:

 private bool IsSafeGrantSet(PermissionSet grantSet, Evidence sandboxEvidence){    // Figure out what the CLR considers a safe grant set    PermissionSet clrSandbox = SecurityManager.GetStandardSandbox(sandboxEvidence);     // We'll consider this safe only if we're requesting a subset of the safe    // sandbox set.    return grantSet.IsSubsetOf(clrSandbox);}

Similarly, if you are a host trying to setup an AppDomain to sandbox assemblies that are coming from the Internet, you might do so this way:

 // Find a safe sandbox set to give to assemblies downloaded// from the internetEvidence internetEvidence = new Evidence();internetEvidence.AddHostEvidence(new Zone(SecurityZone.Internet));PermissionSet clrSandbox = SecurityManager.GetStandardSandbox(internetEvidence); // Create a sandboxed AppDomain to hold themAppDomainSetup sandboxSetup = new AppDomainSetup();sandboxSetup.ApplicationBase = DownloadDirectory; AppDomain sandbox = AppDomain.CreateDomain("Internet sandbox",                                           internetEvidence,                                           sandboxSetup,                                           clrSandbox);