An Overview of Code Access Security


A thorough description of the CAS system is beyond the scope of this chapter. CAS is so broad and so detailed that entire books have been dedicated to the subject. In this section, I focus on the three main concepts in CAS (evidence, policy, and permissions) and describe them from the perspective of their use in extensible applications.

Perhaps the best way to begin describing CAS is by looking back at the original motivation behind the creation of the feature and the scenarios it is meant to enable. The primary goal of CAS is to enable what is referred to as partially trusted code. In the partial trust model, the range of operations that code can perform is related to how much it is trusted by the machine administrator and by the host that owns the process in which it is running. The level of trust, and therefore the operations the code can perform, can be very granular and ranges from no trust to fully trusted. Code with no trust is completely restrictedit can't even run. Code that is fully trusted is allowed to do anything it wants. Allowing levels of trust between full and none is where CAS provides its value. For example, a machine administrator might dictate that a given piece of code can access only a certain portion of the file system and display some user interface, but it can't do anything else, such as access the network, print documents, or store data in the registry.

Indeed, the motivation behind CAS and the partial trust model is to provide a more granular model than the "all or nothing" model used in many existing operating system security models. Consider the case of a control that is downloaded from an Internet site and executed on a local machine. In the purely native code security model, the administrator (and the user) have only two choices when determining how much to trust the control. They can decide either to trust it completely or not trust it at all. Trusting the control completely allows the control to have full access to the machine. Not trusting the control means it won't even be installed. The .NET Framework recognizes that there are many scenarios in which a more middle-of-the-road approach is desired. That is, code can be trusted to perform some operations but not others. These are the scenarios that CAS enables.

In addition to providing a more granular trust model, CAS differs from the existing Microsoft Windows operating system security model in another key way: security decisions in CAS are based on code identities, not user identities. That is, CAS grants code the permission to perform certain operations based on characteristics of the code itself, not based on the user who is logged on to the machine when the code is running. For example, an administrator or host can grant code the permission to access the file system based on whether the assembly has a strong name associated with a particular cryptographic key pair. In contrast, administrators of the Windows security system make decisions about which portions of the file system can be accessed based on the user who is running the code. The characteristics of the code that can be used to make security decisions in CAS are called evidence. Evidence is one of the primary concepts in CAS, as you'll see in the next section.

The Core Concepts: Evidence, Permissions, and Policy

The CAS system is built around the following three primary concepts: evidence, permissions, and policy.

  • Evidence As described, security decisions in CAS are based on characteristics of the code called evidence. Evidence is associated with an assembly and comes from two main sources: the CLR and the extensible application that is loading the assembly.

  • Permissions A permission represents the right to access a given resource in the system. For example, the .NET Framework includes a permission called FileIOPermission that defines which portion of the file system an assembly can access. Authors of extensible applications can also define their own permissions to protect access to application-specific resources.

  • Policy Policy is the mapping between evidence and permissions. CAS policy is a collection of rules (expressed in what are called code groups) that describe which permissions are granted to the assembly based on the evidence associated with that assembly. For example, a policy statement can specify that "all code downloaded from www.cohovineyard.com can execute on my machine, but it cannot access any resources such as the file system or registry." Policy can be defined both by the administrator of the machine and the extensible application that hosts the assembly.

These concepts all work together to determine the set of operations an assembly can perform. Evidence is the input to policy, which specifies which permissions are granted as shown in Figure 10-1.

Figure 10-1. Code Access Security policy grants permissions to an assembly based on evidence.


As you've seen, all three of these primary concepts are extensible. In the next few sections, I describe these concepts in more detail and talk about when you might want to define custom evidence, write a custom permission, or author CAS policy statements for an extensible application.

Evidence

As discussed, the evidence describing an assembly comes from two main sources: the CLR and the extensible application that is hosting the assembly. The CLR assigns a fixed set of evidence to an assembly when it is loaded. This evidence can include the assembly's location on the file system or on the Internet, its strong-name signature, the identity of the assembly's publisher, and so on. Evidence assigned by the host can include these forms of evidence as well, but a host can also define application-specific evidence classes. For example, later in the chapter, I define application-specific evidence that identifies an assembly as contained in a cocoon file from the sample built in Chapter 8.

Evidence, like all of the core concepts in the CAS system, is implemented as managed objects. The evidence that is natively understood by the CLR is represented by managed classes, including the following from the System.Security.Policy namespace:

  • Application Directory

  • Gac

  • Hash

  • PermissionRequestEvidence

  • Publisher

  • Site

  • StrongName

  • Url

  • Zone

Note

An assembly can also provide evidence about itself. However, this evidence is weaker in the sense that any evidence provided by the CLR or the hosting application can always override it.


Let me make the concept of evidence more concrete by giving a specific example. The evidence for a given assembly can be discovered using the Evidence property on System. Reflection.Assembly. The Evidence property is an object of type System.Security.Policy.Evidence that contains a collection of all the evidence associated with an assembly. The program in Listing 10-1 loads an assembly and enumerates its evidence using the Evidence property from the Assembly class.

Listing 10-1. Evidencedisplay.cs
using System; using System.Text; using System.Reflection; using System.Security.Policy; using System.Collections; namespace EvidenceDisplay {    class Program    {       static void Main(string[] args)       {          // Load an assembly.          Assembly a = Assembly.Load("Utilities");          // Get and display its evidence.          Evidence ev = a.Evidence;          IEnumerator e = ev.GetEnumerator();          while (e.MoveNext())          {             Console.WriteLine(e.Current);          }        }     }  }

The preceding program generates the following output:

<System.Security.Policy.Zone version="1"> <Zone>MyComputer</Zone> </System.Security.Policy.Zone> <System.Security.Policy.Url version="1"> <Url>file:///C:/temp/Evidence/Evidence/bin/Debug/Utilities.dll</Url> </System.Security.Policy.Url> <StrongName version="1" Key="0024000004800000940000000602000000240000525341310004000001000100571ED9EF397800C456148B4 CB3F5F1DC73223B883C62E1A7804E80CA2084FEE41D26B233AAF044BA8D6322D1BD78E448F07DFD4B06510A2C87D 1D7DC86F89EAE304A327737B290B9AC20BEB84F132C8B95A7868A8938562027803333381D8DD2A9E4D66A41E1A83 D01F7CE5C01DAC8A4CB9FBD02EEBEBEAB870D8EB291E4FCA6" Name="Utilities" Version="1.0.1613.31500"/> <System.Security.Policy.Hash version="1"> <RawData>4D5.......0000000000000</RawData> </System.Security.Policy.Hash>

As you can see, the CLR has assigned evidence to the Utilities assembly describing the zone and file location from which it originated, its strong-name signature, and a hash of the assembly's contents (I've abbreviated the output to display only a portion of the hash).

Evidence on its own merely describes an assembly. To make any use of evidence, policy must be defined that maps that evidence to a set of permissions. The next section describes CAS policy in more detail, including its role in extensible applications.

Policy

CAS policy is expressed as a collection of code groups that define the mapping between evidence and the set of permissions to grant the assembly. Each code group consists of a membership condition and a policy statement. A membership condition is a qualifier that defines the specific condition that an assembly's evidence must satisfy to be granted the specified set of permissions. The set of permissions to be granted, along with some attributes that describe how these permissions are merged with those from other code groups, is specified in the policy statement. Figure 10-2 shows a sample code group. This group grants all code downloaded from www.cohowinery.com the permission to execute and to read and write to the c:\temp directory in the file system.

Figure 10-2. Code groups consist of a membership condition and a policy statement.


The code groups that constitute CAS policy are arranged in a hierarchy. That is, the code groups form a tree in which a code group can have other code groups as children. The code group at the top of a CAS policy tree always has a membership condition of all code, meaning that every assembly qualifies regardless of its evidence. The All Code code group then has children that define the mappings between specific pieces of evidence and a permission set to grant. An example of a policy tree is shown in Figure 10-3.

Figure 10-3. Code groups are arranged in a hierarchy.


When evaluating policy for a given assembly, the CLR takes the assembly's evidence and compares it against the membership conditions for all code groups in the policy tree. The permission set specified by each code group for which the assembly's evidence qualifies is added to the overall set of permissions that will be granted to the assembly. For example, the policy tree in Figure 10-3 contains two code groups (other than the All Code group). One group (the Coho Winery code group) grants permissions based on the fact that an assembly originated from www.cohowinery.com. The second code group (the Coho Strong Name code group) grants permissions based on whether the given assembly was signed with a specific strong-name key (the Coho Winery key).

To illustrate how the permission sets from multiple code groups are typically combined, assume the CLR is evaluating policy for an assembly from www.cohowinery.com that is signed with the Coho Winery key. The evaluation process begins at the top of the tree. After satisfying the membership condition for All Code, the CLR asks the Coho Winery code group whether the assembly's evidence passes the URL membership condition specified in that code group. Because the assembly originated from www.cohowinery.com (recall that the CLR automatically assigns this evidence), the permissions that grant the right to execute and to read and write from c:\temp are added to the assembly's grant set. Next, the CLR tests the membership condition for the Coho Strong Name code group. This membership condition passes as well because the assembly is signed with the proper key. As a result, the permission to read and write anywhere on c:\ and the permission to programmatically access the www.cohowinery.com site are granted to the assembly as well. The overall set of permissions granted to the assembly includes the following rights:

  • Execute

  • Read and Write to c:\ (this is a superset of the permission to read and write only from c:\temp)

  • Access www.cohowinery.com

In this example, the permissions granted by each qualifying code group were combined (using the mathematical union operation) to form the final grant set. It's typically the case that permission sets are combined in this fashion, but other options are possible. The behavior for how different permission sets are combined is determined by the types of code groups in the policy tree and by the attributes on their policy statements. In our example, all code groups were union code groups. However, there are other types of code groups, including first match code groups, which define different rules for how permission sets are combined.

As with all of the core concepts in CAS, the code groups and membership conditions discussed here are represented as managed objects. The .NET Framework provides membership conditions that correspond to the types of evidence natively understood by the CLR. For example, the ZoneMembershipCondition class in System.Security.Policy corresponds to Zone evidence, the StrongNameMembershipCondition is used to test StrongName evidence, and so on. In addition, the CAS policy system is extensible and allows custom code groups and custom membership conditions to be defined. I take advantage of this capability later in the chapter when I define a custom membership condition to test whether evidence is present that identifies an assembly as coming from a cocoon file.

Policy Levels

The hierarchy of code groups just described constitutes a policy level. The CAS policy system has four such levels:

  • Enterprise Enterprise CAS policy is specified by a system administrator, typically using the .NET Framework Configuration tool, and deployed to the machines in an enterprise using any number of software distribution tools.

  • Machine Administrators can also define CAS policy that applies to all applications running on a given machine. Machine-level CAS policy is also typically specified using the .NET Configuration tool.

  • User CAS policy can also be specified for particular users on a machine. This level of policy enables an administrator to grant different sets of permissions to different types of users, for example.

  • Application domain CAS policy can be specified by the creator of each application domain. By default, each new domain has no such policy. The ability to provide custom policy per application domain is one of the primary techniques an extensible application can use to customize the CAS system. Later in the chapter, I define application domain CAS policy for the domains I create as part of the cocoon CLR host.

The CLR evaluates all four of these policy levels to determine the set of permissions that should be granted to an assembly. The four grant sets that result from evaluating the individual policy levels intersect to determine the assembly's final grant set as shown in Figure 10-4.

Figure 10-4. The grant sets from each policy level intersect to determine the set of permissions granted to the assembly.


The intersection of the grant sets from the individual policy levels results in a least-common-denominator approach to determining the final set of permissions granted to the assembly. As a result, no level can force a particular permission to be granted. A permission must be granted by all other levels for it to be part of the final grant set.

The caspol.exe tool that ships with the .NET Framework SDK contains some options you can use to evaluate the enterprise, machine, and user policy levels statically for a given assembly. These options are useful both to understand how the policy system works in general and to diagnose why the policy system isn't granting an assembly the set of permissions you expect. The -rsp (stands for "resolve permissions") flag to caspol.exe displays the results of evaluating policy for the assembly you provide. For example, the following output was generated by running Caspolrsputilities.dll from a command prompt:

Microsoft (R) .NET Framework CasPol 2.0.40301.9 Copyright (C) Microsoft Corporation 1998-2004. All rights reserved. Resolving permissions for level = Enterprise Resolving permissions for level = Machine Resolving permissions for level = User Grant = <PermissionSet     version="1"> <IPermission     version="1"    Read="USERNAME"/> <IPermission     version="1"    Unrestricted="true"/> <IPermission     version="1"    Flags="Assertion, Execution, BindingRedirects"/> <IPermission     version="1"    Unrestricted="true"/> <IPermission     version="1"    Level="DefaultPrinting"/> <IPermission     version="1">    <Machine name="." access="Instrument"/> </IPermission> </PermissionSet>

As you can see from this output, the intersection of the enterprise, machine, and user policy levels grants the Utilities assembly the permission to read the USERNAME environment variable, display user interface, use the printer, write to the event log, and so on .

Note

As discussed, the core concepts of CAS are all represented as managed objects. So their on-disk representation is just the serialized form of the managed object. You can see this representation both in the preceding output from caspol.exe and in the output from the evidence display program listed earlier in the chapter. Running caspol rgs determines the final grant set for the assembly and outputs it using .NET serialization. The policy trees that represent the enterprise, machine, and user policy are also serialized to disk in this way. For example, you can look at the serialized form of the machine policy tree by looking at the security.config file in the %windir%\microsoft.net\framework\v2.0.41013\config directory. The .NET Configuration tool simply edits the managed objects in memory and persists them to disk files using .NET Framework serialization.


Although caspol.exe can't be used to evaluate application domain CAS policy, it is useful to help debug extensible applications nonetheless. For example, you might expect that the code in your application domain should get a specific set of permissions as specified by your application domain CAS policy. However, if the enterprise, machine, or user level doesn't also grant the permission you do at the application domain level, the permission will not be included in the assembly's final grant set. Using caspol.exe to evaluate the grant set produced at the enterprise, machine, and user levels is a great way to diagnose these sorts of issues.

Default CAS Policy

The .NET Framework ships with default security policy for the enterprise, machine, and user levels. Generally speaking, default policy grants full trust to all code that is installed on the local machine and to all assemblies that ship as part of the .NET Framework. Code that is loaded from the Internet, an intranet, or file shares gets a reduced set of permissions. As demonstrated, it's useful to understand the contents of the enterprise, machine, and user levels of policy because policy does have an effect on the permission you'll grant as part of your extensible application. CAS policy, whether it's the default policy shipped with the .NET Framework or a set of policy explicitly provided by an administrator, is best viewed with the .NET Configuration tool. Figure 10-5 shows the code group hierarchy representing machine policy as displayed in the Configuration tool.

Figure 10-5. The .NET Configuration tool makes it easy to view security policy for the enterprise, machine, and user levels.


Permissions

As described, a permission represents the right to access a particular resource. Permissions are implemented as managed objects just as the other core concepts of the CAS system are. As demonstrated, permissions are granted to an assembly as a result of evaluating CAS policy based on the evidence associated with that assembly. In the next section, I discuss how the CLR enforces that the resources protected by a permission cannot be accessed by unauthorized code.

The .NET Framework includes several permissions that protect the resources on a typical computer. I gave some examples of these permissions earlier when I discussed the results of evaluating security policy for a particular assembly using caspol.exe. The list of permissions included with the .NET Framework includes the following:

  • FileIOPermission

  • RegistryPermission

  • EnvironmentPermission

  • ReflectionPermission

  • WebPermission

  • SocketPermission

  • UIPermission

  • SecurityPermission

As with most aspects of the CAS system, the set of permissions that can be granted to an assembly is completely extensible. If you have a resource to protect that isn't covered by one of the .NET Framework permissions, you simply write your own. The permissions you write can be granted by CAS policy and are enforced by the CLR just as the .NET Framework permissions are. I won't cover the details of how to write a custom permission in this book, but more information and examples can be found in the .NET Framework SDK and in any of a number of books dedicated to CAS.

Runtime Enforcement of Permissions: Permission Demands and the Stack Walk

At a high level, a security system performs the following three activities to ensure the protection of a particular resource:

  1. Authentication

  2. Authorization

  3. Enforcement

Authentication, or the secure identification of the entity attempting to access a protected resource, is accomplished in CAS through the assignment of evidence to an assembly. The assignment of rights to the secure identity, or authorization, corresponds to the evaluation of CAS policy. In the last few sections, I discussed how evidence is used by the policy system to determine the set of permissions an assembly is granted. In this section, I show how the CLR enforces that a given assembly can access only the resources for which it has permission.

The enforcement of permissions by the CLR is done using three techniques: validation, verification, and stack walks. Validation and verification refer to the steps taken to ensure the correctness of an assembly. Validation ensures that the assembly's metadata and intermediate language (IL) stream are well formed. That is, the metadata doesn't include pointers to random memory locations in the file and that all IL instructions performed by the assembly are correctly formed. Verification ensures that the code in the assembly is type safe. In Chapter 5 I discuss the importance of type safety in ensuring that the isolation boundary provided by an application domain is sound. The final technique used to enforce the correctness of the CAS system is the stack walk. Simply put, a stack walk ensures that the assembly attempting to access a particular resource (and all of its descendents on the call stack) has been granted the permission required to access the resource.

The process of enforcing permissions through a stack walk begins with the demand of a permission by the class library that provides the managed API over the protected resource. This process works as follows. Resources protected by CAS permissions always have a managed API that is used to access them. For example, the .NET Framework includes a set of APIs to access the file system. These APIs, contained in the System.IO namespace, protect access to the file system using the FileIOPermission. Similarly, the .NET Framework APIs for accessing the registry protect the underlying resource using RegistryPermission. These class libraries protect resources by issuing a demand for the appropriate permission. For example, Figure 10-6 shows a class library containing a class called File that applications use to access the file system. The Read method on the File class issues a demand for FileIOPermission before reading the contents of the file.

Figure 10-6. Class library authors demand permissions to protect resources.


A permission demand is an indication to the CLR that it should check to make sure that the caller attempting to access the resource has been granted the appropriate permission. In other words, the demand of a permission initiates a stack walk. When walking the stack, the CLR checks the assembly issuing the demand and all other assemblies in the call stack to make sure that they have been granted the appropriate permission, FileIOPermission, in this case. If all assemblies have the proper grant, the demand passes and execution proceeds. However, if any assembly in the call chain has not been granted the required permission, execution stops and an instance of System.SecurityException is thrown. The process of walking the stack in response to a demand for a permission is shown in Figure 10-7.

Figure 10-7. The CLR walks the stack to ensure all callers have been granted the demanded permission.


In the general case, a stack walk involves checking all callers on the stack as I've just described. However, CAS involves various concepts that can introduce variations on the basic stack walk. For example, demands such as link demands and inheritance demands check only an assembly's immediate caller instead of all callers on the stack. In addition, you can use various APIs to control how the stack walk is performed. Examples of these stack walk modifiers include the assert, the deny, and the permit only.

There is much more to the CAS system than what I have presented here. However, this introduction should provide enough background for you to be able to add the basic CAS concepts to an extensible application. Now that I've covered the basics, take a look at the specific APIs the .NET Framework provides for customizing evidence, permissions, and policy.



    Customizing the Microsoft  .NET Framework Common Language Runtime
    Customizing the Microsoft .NET Framework Common Language Runtime
    ISBN: 735619883
    EAN: N/A
    Year: 2005
    Pages: 119

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net