[ Team LiB ] |
7.1 Permissions ExplainedThe goal of CAS is to give security administrators (and users) fine-grained control over the actions and resources to which code has access. For example, a security-conscious administrator may want to stop applications run from the Internet from starting new processes or allow only code written by Microsoft to be able to write to the Windows registry. Instead of defining a fixed set of operations and resources to which access can be controlled, CAS provides a flexible and extensible framework that uses objects called permissions to define and enforce security. Permission objects serve the following purposes:
Figure 7-1 illustrates the use of permission objects to enforce code-level security and demonstrates the most common CAS operation—the security demand. The runtime grants each of the application's assemblies a set of permissions at load time, which the runtime represents with a set of permission objects that it associated with the assemblies. When an assembly's code calls a protected library member, the library uses a permission object to demand that the runtime ensure that the calling assembly has the equivalent permission. The runtime evaluates the permission objects associated with the assembly and confirms to the library whether the assembly has the specified permission. Based on the response, the library determines whether to complete the application's request. Figure 7-1. The security demandAs Figure 7-1 shows, it is the library's responsibility to invoke the facilities provided by CAS to ensure that the calling application has the necessary permission to access its functionality. The library must also determine the appropriate action to take if the security demand fails. We will discuss this process in Section 7.1.4, later in this chapter. 7.1.1 Granting PermissionsThe functionality accessible to the code in an assembly is represented by the set of permission objects assigned to it by the runtime. The runtime determines which permissions to grant to an assembly (the grant set) based on the assembly's identity. The runtime establishes the identity of an assembly using the evidence the assembly presents when it is loaded; we discuss evidence and code identity in Chapter 6. As shown in Figure 7-2, the runtime uses two different mechanisms to translate evidence into permissions. Figure 7-2 also shows that there are two types of permissions, which we discuss in Section 7.1.3, later in this chapter. Figure 7-2. Sources of permissionsThe two mechanisms depicted in Figure 7-2 work as follows:
7.1.2 Requesting PermissionsConfiguring security policy can be complicated, and problems arise when the security policy does not grant legitimate assemblies enough permission to function correctly. For example, without permission to establish network connections to an email server, an email client application cannot send and receive email. In an ideal security configuration, it is also important to make sure assemblies do not have more permissions than they need. This reduces the chance that bugs in the assembly can damage important resources. It also reduces the chance that malicious code can use the assembly as a gateway through which to access protected functionality. CAS supports the following three permission request operations to address the issues associated with assemblies having too many or too few permissions:
We describe the techniques used to make these requests in Section 7.2.4. 7.1.3 Permission TypesThe .NET Framework class library uses permissions to control access to the functionality it provides. The designers of the .NET class library have made decisions about which members of which classes must have restricted access and have implemented a set of permission classes through which they secure that functionality. The runtime also uses permissions to restrict access to a handful of operations that are critical to the security of the overall runtime environment. These operations include the ability of code to execute, to run unmanaged code, and to skip verification. The permission classes implemented in the class library of the .NET Framework fall into three categories: code-access permissions, identity permissions, and role-based permissions. Role-based permissions represent the user context in which code is executing. For example, the user Gary is running the application or an application is running as the inbuilt Windows system account. We discuss role-based permissions in Chapter 10 along with the other role-based security features provided by the .NET runtime. In the following sections, we introduce the code-access and identity permission classes contained in the .NET class library.
7.1.3.1 Code-access permissionsCode-access permission classes represent actions and resources that are subject to security control. The .NET class library includes the code-access permission classes listed in Table 7-1. Some permission classes are members of the System.Security.Permissions namespace, while others are members of the namespace containing the functionality to which they apply. We break down the classes by namespace and highlight those permissions new to Version 1.1 of the .NET Framework. All of these classes implement a common set of interfaces and adhere to a common design pattern. It is through the methods exposed by the code-access permission classes that code invokes the runtime to enforce code-level security. We discuss the specific functionality of code-access permission classes later in Section 7.2.2.
Each of the standard code-access permission classes controls access to a range of related functionality and provides fine-grained control over the actions and resources a particular permission object represents. For example, the SecurityPermission class represents access to a set of discrete security related functions, such as the ability to control evidence or the ability to execute unmanaged code. In contrast, because the FilIOPermission class must represent different types of access (read, write, append) to everything from a specific named file or directory to the entire hard disk, it can represent an infinite variety of resource access. Each of the standard code-access permission classes can also represent a completely restricted state and a completely unrestricted state. For example, an unrestricted FileIOPermission represents full access to all files and directories on the local hard drives, whereas a completely restricted FileIOPermission represents no access. 7.1.3.2 Identity permissionsIdentity permissions represent the value of certain types of host evidence an assembly presents to the runtime when it was loaded. Table 7-2 lists the standard identity permission classes provided in the .NET class library and shows the type of evidence they represent; all identity permission classes are members of the System.Security.Permissions namespace. We discussed each of the evidence classes in Chapter 6.
Because identity permissions implement the same interfaces as code-access permissions, you can use them in the same ways. Identity permissions are a powerful and convenient way to leverage the functionality of the CAS security framework and make security decisions based on the identity of an assembly, avoiding the need to work directly with evidence objects. For example, you can use identity permissions to enforce the following types of security restrictions:
7.1.4 Enforcing Code-Access SecurityThe security demand, which we mentioned in the introduction to this section, is the principal mechanism used to enforce code-level security. When code makes a security demand, it instructs the runtime to ensure that the code that called it has a specified permission object (or set of permissions). Figure 7-3 shows what happens when a simple console application named MyApp.exe tries to delete a file named C:\Test.txt using the static System.IO.File.Delete method from the .NET class library. Figure 7-3. The security demand processThe security demand process shown in Figure 7-3 works as follows:
In this example, MyApp is the only executing application and is the only assembly that you are concerned with. However, when code makes a security demand, the runtime must ensure that not only the immediate caller has the specified permission, but that the whole chain of previous callers have the necessary permission as well. This ensures that less-trusted code does not exploit more highly trusted code to perform operations to which it does not have permission (commonly known as a luring attack). The runtime checks the permission of all callers using a stack walk, which we describe in the next section. We discuss the implementation of security demands in Section 7.2.5. 7.1.4.1 Stack walks explainedThe runtime maintains a call stack for each thread of execution. When an active thread calls a method or function, the runtime adds a new activation record (also called a stack frame) to the top of the thread's call stack. The activation record contains details of the method that was called, the arguments passed to the method, and the address to return to when the method completes. Upon completion of the called method, the runtime removes the activation record from the top of the stack and returns control to the instruction specified in the record. The runtime also records application domain transitions on the call stack. As code running in one application domain calls a method in another application domain, the runtime records the transition by adding a record to the call stack. We discussed application domains in Chapter 3. The call stack grows and shrinks constantly throughout the life of an application, but at any instant, the call stack contains an accurate record of the sequence of method calls and application domain transitions that have brought the code to its current point of execution. Figure 7-4 shows the state of call stack after a sequence of method calls (shown with arrows), which cross assembly and application domain boundaries. Figure 7-4. Structure of a call stackWhen code makes a security demand, the runtime walks back up the call stack (hence the name stack walk), from the most recent activation record to the oldest. Importantly, stack walks do not include the activation record of the method making the security demand; the first activation record evaluated is that of the immediate caller. As the runtime walks the stack, it compares the permissions granted to each method and application domain to see whether they contain the demanded permission. Remember that the permissions of a method are the same as the permissions of the assembly in which it is contained. If every method and application domain on the call stack has the demanded permission, then the stack walk completes without event; execution returns to the method that made the security demand, which in turn carries on to complete the protected operation. However, if the runtime encounters a method or application domain without the demanded permission, the runtime throws a System.Security.SecurityException back to the method that made the security demand, which would normally pass this through to the code that called it.
Figure 7-5 shows a stack walk that results when MethodD from Figure 7-4 calls the File.Delete method. The Delete method demands that callers have the FileIOPermission permission to write to a specified file. In response to the security demand, the runtime walks the stack and inspects the permissions granted to first MethodD, and then MethodC, MethodB, AppDomainY, MethodA, and finally AppDomainX. As long as all of them have the necessary FileIOPermission, the Delete call is permitted. Figure 7-5. Walking the stackSome important points to note:
The stack walk process described here is the default behavior of the runtime in response to a security demand. You can override the default behavior of the stack walk process in three ways:
We discuss each of the Assert, Deny, and PermitOnly overrides further in Section 7.2.6, where we provide implementation examples. |
[ Team LiB ] |