What’s New in Code Access Security in .NET Framework 4.0 – Part I

The Code Access Security model has been completely redesigned in the .NET Framework 4.0, to the point where CAS policies have been completely removed, and everything now works through Level2 Security Transparency. Confused? Not for long. Matteo Slaviero, a .NET security expert, brings us up to speed.

As many of you probably already know, Code Access Security (or CAS, for short) is a security technology developed to provide the ability to protect system resources when a .NET assembly is executed. Such system resources could be: local files, files on a remote file system, registry keys, databases, printers and so on. Unfettered access to these types of resources can lead to potential security risks, as malicious code could perform damaging operations on them, such as removing critical files, modifying registry keys, or deleting data stored in databases to suggest just a few.

Thankfully, CAS can mitigate those security risks by giving us fine-grained control over which resources can be accessed, and who can access them. Unfortunately, using CAS features in .NET Framework is not always so easy, and a lot of work is typically needed to implement them in a correct manner. For this reason, Microsoft has changed CAS a lot in .NET Framework 4.0, with the goal of making it easier to implement and manage.

This article is the first of two, in which we will introduce the new Code Access Security model of .NET Framework 4.0 and how it changes the way in which software has to be developed when security is a fundamental priority. We will see the new Security Transparence model of .NET Framework 4.0, and how it was designed to eliminate the need for the CAS Policy model, which was used until .NET Framework 3.5. We will see how important the host in which protected code runs has become important, and how the host and assemblies (whether they’re your own or 3rd party) interact with each other to control protected resources. In the next article we will go deeper into the assembly’s structure, focusing our attention on protected assemblies’ methods and how to implement a more sophisticate, yet granular, protection strategy for them.

We’ll start this article by describing CAS before the .NET 4.0 Framework, with the intent of  making the changes applied in the new model as clear as possible. Next, we will cover the fundamentals of the new CAS system, providing, at the very least, what you need to get started with it.  Finally, we will analyze how hosts change the way in which CAS has to be applied, and how to sandbox untrusted assemblies to prevent possible malicious code from running. To help describe the new security model and how it is likely to affect our assemblies, we’ll run a few demonstrations as examples, and you can find them in the supporting documents at the top of this article.

Code Access Security Before .NET Framework 4.0

Here’s a very brief description of how, prior to .NET Framework 4.0, Code Access Security allowed developers and system administrator to protect resources by defining:

  • A set of Permissions that an assembly or a method must have in order to access critical (from a security perspective) resources. A full list of permissions defined on the .NET Framework can be found over at MSDN.
  • PermissionSets, which are collections of two or more Permissions.
  • An assembly property called Evidence, which acts as a sort of combination of the identity of the assembly in relation to the zone from which it came from (current machine, intranet etc.), the identity of the assembly’s publisher (obtained using a digital certificate to sign the assembly), and identity of the assembly itself (its strong name or its hash) or simply its location.
  • A set of Code Groups, which contain all the assemblies that possess specific Evidence.  Every Code Group has a specific PermissionSet assigned.

While Evidence is assigned by the run-time every time an assembly executes, Code Groups and the related PermissionSet are stored inside the machine, and they can be modified or newly created by system administrators. Developers are able to interact with the permission assigned to their assembly in one of two ways:

  1. Declaratively: by using a set of attributes that can be assigned to an assembly or to its classes and/or methods (properties accessors included).
  2. Imperatively: by using a set of classes inside an assembly’s methods.

For example, to control the access to the file system, FileIOPermissionAttribute or an instance of the FileIOPermission class can be used. The first is used declaratively, the second imperatively.

To check permissions assigned to their code, developers can invoke methods such as Demand(), LinkDemand() or InheritanceDemand() (the last two only declaratively), or override them by using methods such as  Assert() , Deny() or PermitOnly().

When an assembly loads, the .NET Framework run-time checks its Evidence and assigns to it only the specific permissions allowed for its Code Group. If the assembly has all the permissions granted, it is said to be fully trusted, otherwise it is said to be partially trusted. That’s all  you need to know about it for now, because this system is fiendishly difficult to use effectively, and the new CAS security model in the .NET Framework 4.0 completely replaces it.

What’s new in the .NET Framework 4.0

The changes in .NET 4.0 are largely reactive, in that they address existing problems rather than implementing new features. Over the years, the CAS model, as implemented in the pre-4.0 version of the .NET Framework, has revealed some problems which are not so trivial to solve. In particular:

All the work that must be done to setup a successful CAS Policy, that is, all the work needed to define the right PermissionSet and Code Groups for each specific machine. This discouraged a lot of administrators from implementing the technology on their systems.

  • When a specific application needs to be moved onto a different system, the different security policy applied to this new system could cause malfunctions in the application itself. For example, if an executable file was able to run properly on the developer’s machine, sometimes, when it needed to be moved on to a production server or a remote share, the same executable could suddenly stop working.
  • When developing code, it was not so easy to set up the CAS features for the assembly. This was because administrators needed to set up very different CAS policies on their machines, and the developer had to keep in mind all the possible locations their assemblies might be run, as well as what the possibly PermissionSets were. Developers had no way of knowing the administrators decisions in advance.
  • CAS policies were very useful when administrators needed to control what software could and could not do, but CAS policies had no effect at all on unmanaged code.

So, the Microsoft .NET Security Team decided to rebuild Code Access Security from the ground up. The main differences can be boiled down to:

  1. All of the CAS policy system has been completely removed. Decisions about what permissions can be granted to an assembly are now taken by the host in which the assembly runs. This eliminates all problems related to CAS Policies setup.
  2. The enforcement mechanism, that is, the mechanism used by the run-time to force an assembly to execute only code that has permission to execute, has been replaced by the Security Transparent model. This simplifies a lot of the work needed to set the access conditions for the resources that the assembly has to use.

The Security Transparent model was introduced in the .NET Framework 2.0 but, until the 4.0 version, it could only be used at the assembly level, and it was mainly used to prevent security transparent code from elevating privileges. In fact, Security transparent code could not even use the Assert method. In pre-4.0 versions of the .NET Framework, transparency could not be used for enforcement, as enforcement was handled by the CAS Policy system; this behavior is now called Level1 Security Transparency. With .NET Framework 4.0, these limitations have been removed and the Security Transparent model became the standard way to protect resources. The new model has been called Level2 Security Transparency, and now we’ll take a look at how it works.

The Level2 Security Transparent Model

Level2 security transparent model divides all code in three categories:  SecurityCritical code, SecurityTransparent code and SecuritySafeCritical code. Let’s see in detail what they can and cannot do:

  • SecurityCritical:
    SecurityCritical code is full trusted. Such code can be called by other SecurityCritical code or by SecuritySafeCritical code, but cannot be called by SecurityTransparent code.
  • SecurityTransparent:
    SecurityTransparent code has limited privileges on the system resources, and has no access to SecurityCritical code. Moreover, it cannot call native code nor elevate permissions.
  • SecuritySafeCritical:
    SecuritySafeCritical code provides a sort of bridge between SecurityTransparent code and SecurityCritical code. In fact, SecurityTransparent code can call SecuritySafeCritical code, which in turn can call SecurityCritical code. SecuritySafeCritical code is considered fully trusted, and has the same permission of the SecurityCritical code.

Note that due to the fact that SecurityTransparent code cannot call SecurityCritical code, Level2 Security Transparence became an enforcement mechanism.

1058-MS1.JPG

Figure 1. The relationships between the three types of security categories

Let’s start with some examples to demonstrate the model. Say we start writing a simple console application that helps us to explore the security settings of an assembly which we write. To do this, we use the following new properties of .NET Framework 4.0:

  • Assembly.SecurityRuleSet:
    This states which security rule is used on our assembly: Level1 Security Transparence or Level2 Security Transparence.
  • Assembly.IsFullTrusted:
    If true, the assembly is executing as a fully trusted assembly, and all of its methods are SecurityCritical. If false, the assembly is partially trusted, and all its methods are SecurityTransparent.
  • Type.IsSecurityCritical:
    If true, the object is running as SecurityCritical code.
  • Type.IsSecuritySafeCritical:
    If true, the object is running as SecuritySafeCritical code.
  • Type.IsSecurityTransparent:
    If true, the object is running as SecurityTransparent code.

Initially, we write a simple dll library that contains the following class:

The GetCasSecurityAttributes() method inside the class returns a string that contains:

  • The rule set for the assembly (Level1 Security Transparence or Level2 Security Transparence)
  • Whether the assembly is Full Trusted
  • Whether the AssemblyInfo class is SecurityCritical, SecurityTransaprent or SecuritySafeCritical
  • The number of permission in the assembly’s PermissionSet.

We then create a console application that consumes the AssemblyInfo class exposed by the previous library:

The main method writes the value of the assembly’s Zone Evidence to the console, and then it calls the  GetCasSecurityAttributes() method of the AssemblyInfo class contained in the dll. When we run our program, the output that we get is:

1058-MS2.JPG

Figure 2. The output of the simple demonstration program running on the local machine.

As we can see from Figure 2:

  1. The Evidence for the assembly states that it is running on the local machine.
  2. The security transparency is given as Level2 Security Transparence. We haven’t set it in our code, so we can see that Level2 is the default Security Transparence mode in .NET Framework 4.0. If we want to use the previous Level1 model, we can use the assembly’s attribute:

    Pay attention to the fact that, by doing that, the assembly becomes transparent, but our program will still execute. This is because Level1 Security Transparency is not able to perform enforcement on our code because, as I mentioned previously, enforcement with the pre-4.0 versions of .NET Framework was handled by the CAS Policy. We will soon see that this lack of enforcement doesn’t happen with Level2 Security Transparence, and if you want to maintain compatibility with the pre-4.0 versions of CAS, you must activate the CAS Policy by adding the following lines to the assembly configuration file:

  3. The assembly is fully trusted. This means that all the classes inside it are SecurityCritical, and that no permissions are set to the assembly. This occurs because assemblies that run on a computer or on a shared folder are considered unhosted applications. To explain what that actually means, remember that we saw earlier that, with Level2 Security Transparence, permissions to an assembly are now decided by the assembly’s host, not by the CAS Security Policies. Examples of a host include an ASP.NET run-time, SQL CRL run-time and so on. Applications that run outside such host are called unhosted applications, and they are always fully trusted by default.

    This seem to be a natural decision that the .NET Framework Security Team has taken. If the permissions are all defined on a host, this mean that unhosted applications, for which permission cannot be set, can be either fully trusted or absolutely untrusted (that is, without any permission to access protected resources). In the first case, resources can still be protected by using other technique or tools, and we will see some of them in the next paragraph. In the second case, to allow the .NET Framework to continue working using protected resources would require techniques or tools which would be able to elevate permissions. From a security point of view, this second option would clearly not be a good choice.

    Another advantage of unhosted applications being fully trusted is that, in this way, they can run as SecurityCritical code and cannot be accessed by SecurityTransparent code. So, code that belongs to, for example, the internet zone (which is SecurityTransparent) cannot make use of unhosted applications to damage our systems. As we already know, SecurityTransparent code cannot access SecurityCritical code, and so our unhosted application is therefore protected from code that came from internet or is otherwise suspicious.

    Now, suppose that we would like to run our .exe from a network shared folder. The output which we would then obtain would be:

    1058-MS3.JPG

    Figure 3. The output of the simple demonstration program running on a network shared folder.

    We can see from Figure 3 that the assembly zone evidence has changed to “Internet”, but our assembly is once again fully trusted, and the classes inside it are SecurityCritical; no permissions are applied to the assembly.

    With the pre-4.0 versions of the .NET Framework, the different zone evidence would imply that a different Code Group would be applied to the assembly and, in some situation, the same code would inexplicably stop working. If, for example, the exe should need to access some file, it could do that on the local machine (MyComputer zone), but when moved to a shared folder (Internet Zone) it would throw a security exception, stopping the execution. It may initial seem that there is minimal security in place with this new system, but let’s take a look at how we can keep our code under tighter control.

Reducing permissions in the Level2 Security Transparent Model

From the previous example, the first thing that might come in mind is that, with the new Level2 Security Transparence, the overall security of systems seem to be diminished. By ignoring the principle of least privilege, the new model will surely result in more code than before with the full trust to execute? This is really not the case; the overall security model has only changed, becoming easier to implement, and so reducing the chance of potentially dangerous errors. In a moment, we’ll see how we can reduce the permissions of code running in the Level2 model. However, If administrators want to control which type of code can run on a particular system, they can use tools such as Software Restriction Policies or the new AppLocker available from Windows 7 and Windows 2008 Server R2. Using these newer tools, they can control not only managed code (as the legacy CAS Policy enabled), but even unmanaged code.

From a developer point of view, when it comes to reducing permissions, it is now possible to run application as SecurityTransparent code. if the application doesn’t need to access protected resources, then this a good way to satisfy the guidelines of the principle of least privilege. To force the assembly to run as SecurityTransparent, we just need to insert the following attribute for the assembly:

This states that all code, even if the assembly is fully trusted, will be of the type SecurityTransparent.  If we add this previous line to our demo assembly, we get the following output:

1058-MS4.JPG

Figure 4. The output from our demonstration program with the code set to SecurityTransparent.

As we can see, our assembly is now SecurityTransparent, even though it is fully trusted due to the fact that it is also unhosted. As a result, it can only call SecurityTransparent code and so it cannot be used to access protected (SecurityCritical) resources. In fact, as we see, when it tries to get the Permission Count an exception occurs, because the code contained in the get accessor of the PermissionSet property is SecurityCritical code. The same also happens if the .exe is executed from a network shared folder.

The examples provided in this section seem to state that Level2 Security Transparence is, de facto, an all or nothing model. If the assembly is fully trusted it can do anything, and if we set it to be SecurityTransparent it cannot use protected resources at all. However, a more granular approach is possible when we need to protect specific resources, and it is based on the Allow Partially Trusted Caller Attribute (APTCA) which we can set for an assembly. With it, we can set code as SecuritySafeCritical, thereby creating a bridge between SecurityTransparent and SecurityCritical code. We will discuss this in detail in the next article.

Sandboxing

This is all fine if we’re only working with our own code, but what if we have to use a third party assembly that we doesn’t fully trust? We know that, if we run it on our machines and a SecurityTransparent attribute was not specified inside the assembly, it can do anything with our resources.

The solution is to sandbox the assembly, which restricts which resources the assembly can use, enabling us to protect our systems. Sandboxing consists of the creation of a partially trusted host and forcing the assembly to run inside it. As I mentioned at the beginning of this article, the Level2 Security Transparence replaced the CAS Policy, leaving the host with the ability to set permissions, so this is quite an elegant method of creating a sandbox, as we’ll see in a moment. The partially trusted host is created with the AppDomain.CreateDomain() method defined in the .NET Framework 4.0; This method is not new in the 4.0 version, it has just been modified to permit the sandboxing.

In the pre-4.0 application domain, permissions to access resources were determined by the CAS Policy, which, for each assembly loaded into the domain, applied restrictions to it based on its Code Group, which in turn was determined by its Evidence, and the PermissionSet imposed on the Code Group itself. This lead to a heterogeneous domain, in which PermissionSets could mix each other’s configurations, bringing about very complex situations. With Level2 Security Transparence, permissions are imposed directly to the domain, and all the assemblies inside it are forced to follow them (exceptions are made for those that the developer decides can be fully trusted). This has been called a Homogeneous Domain.

Let’s resume working on our demonstration dll library, and try to run it in a sandboxed (i.e. partially trusted) domain. To do so, we need to modify the AssemblyInfo class by allow it to derive from MarshalByRefObject, and need to use the AppDomain.CreateDomain() method. Before we can use this method, we need to create a PermissionSet which we would like to be granted to the newly-created domain, and so, with the Homogeneous Domain behavior of the Level2 Security Transparent model, to the assemblies loaded on it.

Rather that specify all the permission one by one which we would like to insert into the PermissionSet, we can use the new 4.0 Framework SecurityManager.GetStandardSandbox() method, which allows you to return the associated PermissionSet with the evidence passed as input. The following code shows how to do this:

The line of code above returns a PermissionSet object that contains all the permissions associated with the MyComputer Zone Evidence. We then create a method that browses the security features of the domain that we will create:

Now we can implement our Main method:

Just to explain what’s happening, the main method:

  1. Creates an AssemblyDomainSetup object and set its ApplicationBase value to the directory that contains our demo assembly.
  2. Creates the domain…
    • naming it “CasHostDemo”,
    • without passing an Evidence object
    • using the AssemblyDomainSetup object created in the previous step, and
    • setting the PermissionSet obtained with the GetPermissionSet() method which we developed.
  3. Uses the Activator class to create an ObjectHandler that keep the reference to an object of type AssemblyInfo (defined on our demo dll), then unwrapping it into an AssemblyInfo object.
  4. Calls our GetDomainInfo() method by passing it the domain we’ve created.
  5. Calls the GetCasSecurityAttributes() method of the AssemblyInfo object instantiated.

Note that we don’t pass any Evidence objects to the AppDomain.CreateDomain method, because it does not need them anymore. Given that Evidence is no longer used to assign the correct Code Group to the Domain using CAS Policies, the Evidence  is simply no longer needed.

When we run our program now, we get the following output:

1058-MS5.JPG

Figure 5. The demonstration program running in a Sandbox on our local machine.

As we can see, using the MyComputer Zone as Evidence doesn’t affect our code. In fact, this Zone creates a Full Trust domain without permissions from the PermissionSet. However, if we change the MyComputer Zone to, for example, the Internet Zone, we get:

1058-MS6.JPG

Figure 6. The demonstration program running in a Sandbox in the Internet Zone.

The domain now runs as partially trusted domain, and there are 7 permissions granted (the same 7 permission related to the Internet Zone), meaning that our assembly runs now as partially trusted assembly. All classes are SecurityTransparent and the accessor for the PermissionSet property of the assembly throws the same exception seen previously in Figure 4. We have created a sandbox for the assembly.>

I’ll finish this article by pointing out that the sandboxed domain allows for the possibility of running assemblies as fully trusted, even if the domain is only partially trusted: assemblies contained on the Global Assembly Cache (GAC) run in fully trusted mode by default. If we want to add a non-GAC assembly to the list of fully trusted assemblies, we just need to inform the Assembly.CreateDomain() method about it by listing this non-GAC assembly using its StrongName. Obviously, the assemblies must therefore be signed with a strong name key file. To do this, we modify the previous method as follows:

In this new main method, we load the demo assembly from file System and get its StrongName:

Then, we use a different overload of the AppDomain.CreateDomain() method, which allows us to set which assemblies must be considered full trust, and we pass it the StrongName of the demo assembly.

By running our .exe, we get:

1058-MS7.JPG

Figure 7. Our demonstration program, running in a sandbox with full trust.

We can see that, while the domain remains partially trusted, the assembly runs in full trust mode and all the classes inside it are SecurityCritical.

Conclusion

In this article we looked at how the new Code Access Security model works in the .NET Framework 4.0, and we saw that things have changed a lot, compared to the pre-4.0 versions. This is largely due to the fact that the previous model had some serious limitations (complexity, above all) which were not so easy to change without an overhaul.  From the developers’ perspective, migration of our code to the new Level2 Security Transparences model will be a tough task to accomplish, and in some cases, re-engineering parts of their application could well be necessary.

Thus far, we’ve been introduced to the general concept of the new CAS model, but the analysis stops at the assembly boundary. We looked at transparence, how to use it at the assembly and class level, and how assemblies interact with hosts. We’ve also looked at a few examples of how the new CAS model behaves by default, and from these examples we noticed that the new Level2 Security Transparence model seems to be an all or nothing model. If the assembly is fully trusted, all the system resources can be accessed, and if it is partially trusted, none of them can be used. Despite appearances, this is not actually the case, and in the next article we will see how the Level2 Security Transparence model can be applied in a more granular way by using the Allow Partially Trusted Callers attribute (APTCA) and SecuritySafeCritical code to apply CAS at the method level.