The 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:
When the runtime loads an assembly, it assigns the assembly a set of permission objects that represent the authority the runtime has granted to the assembly. Most permission objects represent access to actions or resources that are subject to security control, such as the ability to create application domains or the ability to write to the Windows event log. Other permissions represent elements of code identity derived from the assembly's evidence, such as publisher certificates and strong names, which we discussed in Chapter 6.
Code that provides functionality or accessibility to resources that must be protected can use the security framework to demand that any code calling it has a specific set of permission objects. The .NET runtime and class library use this capability extensively to protect access to operations and resources, including the ability to run unmanaged code or access the hard disk and Windows registry.
Figure 7-1 illustrates the use of permission objects to enforce code-level security and demonstrates the most common CAS operationthe 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.
As 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.
The 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.
The two mechanisms depicted in Figure 7-2 work as follows:
The runtime compares all of the assembly's evidence with the .NET security policy, in a process named policy resolution, to determine which code-access permissions to grant to the assembly. We describe security policy and the policy resolution process in Chapter 8. For now, it is enough to understand that security policy is a configurable set of rules that provides a mapping between evidence and permissions, and that policy resolution is the process of applying security policy to an assembly to determine the code-access permissions to grant to it.
Any host evidence that implements the System.Security.Policy.IIdentityPermissionFactory interface results in the runtime granting the assembly an identity permission that represents the type and value of the evidence. Of the seven standard evidence types discussed in Chapter 6, Publisher, Site, StrongName, Url, and Zone evidence result in the creation of identity permissions.
|
Configuring 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:
Based on the premise that it is better for an application not to run than to start and crash because of insufficient permissions, CAS allows assemblies to include minimum permission requests. A minimum permission request is a statement contained within an assembly's metadata that specifies a set of permissions that the runtime must grant to an assembly in order for it to function correctly. After the runtime resolves the permissions for an assembly, if the grant set does not contain all of the requested permissions, then the runtime will throw an exception and will not load the assembly.
To use the email client application as an example, the programmer of the client builds a minimum permission request into the application assembly stating that the application must have permission to establish network connections. If the runtime does not grant permission to create network connections, then the application does not run and the user receives an error message. A security administrator can inspect the permission requests made by the application and adjust his security policy accordingly; we discuss the Permview.exe tool used to do this in Chapter 9.
Over and above core functionality, some applications offer additional but nonessential features. CAS allows assemblies to specify in their metadata a set of permissions that is useful for the assembly to have. The runtime loads the assembly even if it cannot grant these optional permissions, and so the assembly must accommodate situations in which it does not have them. For example, an email client application may allow users to export copies of email to disk if it has permission to write to the hard drive. Without the permission, the mail client still functions. However, it does not present the export option to the user nor does it display an appropriate message when the function is invoked.
The main effect of requesting optional permissions is that the runtime never grants the assembly any permission not listed in the optional-permission set; the maximum grant set possible is the union of the assembly's minimum permissions and optional permissions. The runtime discards any permissions granted to the assembly that fall outside these two groups.
The ability to request optional permissions enables an assembly to place an upper limit on the permissions that the runtime will grant it. However, sometimes you may want to refuse a specific set of permissions. CAS allows an assembly to specify a set of permissions in its metadata that the runtime must never grant it. Even if policy resolution normally results in the granting of a permission, the runtime will remove it when it loads the assembly. This allows you to ensure that nobody can use your code to perform any action controlled by the refused permissions. In the case of the email client application, the programmer may want to ensure that the client never calls unmanaged code and builds the refusal request into the assembly.
We describe the techniques used to make these requests in Section 7.2.4.
The .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.
|
Code-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.
Namespace and class |
Description |
---|---|
System.Data.Common | |
DBDataPermission |
An abstract base class that provides common functionality for data provider permission classes, including the OdbcPermission, OleDbPermission, and SqlClientPermission classes. Note that OraclePermission is not derived from DBDataPermission. |
System.Data.Odbc | |
OdbcPermission (1.1) |
Controls access to data sources through the ODBC data provider. |
System.Data.OleDb | |
OleDbPermission |
Controls access to data sources through the OLE DB data provider. |
System.Data.OracleClient | |
OraclePermission (1.1) |
Controls access to Oracle databases through the Oracle data provider. |
System.Data.SqlClient | |
SqlClientPermission |
Controls access to Microsoft SQL Server databases through the SQL Client data provider. |
System.Diagnostics | |
EventLogPermission |
Controls access to the Windows event log. |
PerformanceCounterPermission |
Controls access to Windows performance counters. |
System.DirectoryServices | |
DirectoryServicesPermission |
Controls access to directory services, such as LDAP and Microsoft Active Directory. |
System.Drawing.Printing | |
PrintingPermission |
Controls access to printers. |
System.Net | |
DnsPermission |
Controls access to network-based domain name servers (DNSs). |
WebPermission |
Controls access to Internet resources. |
SocketPermission |
Controls access to socket-based network communications. |
System.Messaging | |
MessageQueuePermission |
Controls access to the Microsoft message queue. |
System.Security.Permissions | |
EnvironmentPermission |
Controls access to read, create, and change environment variables. |
FileDialogPermission |
Controls access to files and folders by restricting the accessibility and functionality of the standard Windows file dialog box. |
FileIOPermission |
Controls access to the local hard disk by restricting access to create, change, and delete files and folders. |
IsolatedStoragePermission |
Controls access to isolated storage, which we discuss in Chapter 11. |
ReflectionPermission |
Controls access to the reflection functionality provided by the runtime. |
RegistryPermission |
Controls access to the Windows registry. |
ResourcePermissionBase |
An abstract base class that provides common functionality for Windows resource-oriented permission classes, including the EventLogPermission, PerformanceCounterPermission, DirectoryServicesPermission, and ServiceControllerPermission classes. |
SecurityPermission |
Controls access to the set of security features relating to application domains, policy, evidence, threading, principles, execution, infrastructure, .NET Remoting configuration, serialization, verification, and unmanaged code. |
UIPermission |
Controls access to manipulate the user interface and the clipboard. |
System.ServiceProcess | |
ServiceControllerPermission |
Controls access to Windows services entries. |
System.Web | |
AspNetHostingPermission (1.1) |
Controls access to ASP.NET-hosted environments. |
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.
Identity 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.
|
Identity permission class |
Evidence type represented |
---|---|
PublisherIdentityPermission |
Publisher |
SiteIdentityPermission |
Site |
StrongNameIdentityPermission |
StringName |
UrlIdentityPermission |
Url |
ZoneIdentityPermission |
Zone |
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:
To allow only code run from the LocalIntranet security zone to execute your method
To allow only code in an assembly signed with your company's publisher certificate to inherit from your class
To run your application only if it was executed from the web site www.oreilly.com
The 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.
The security demand process shown in Figure 7-3 works as follows:
When you run MyApp, the runtime loads the MyApp.exe assembly, evaluates its evidence, and determines what permissions to grant it. In this example, the runtime grants MyApp.exe the following three permissions, which it creates permission objects for and assigns to the assembly:
A RegistryPermission object that represents permission to read the Windows registry
An EnvironmentPermission object that represents permission to read the USER environment variable
A FileIOPermission object that represents permission to write to the C:\Test.txt file
At some point during its execution, MyApp calls File.Delete to delete the C:\Test.txt file.
The File.Delete method creates a FileIOPermission object that represents permission to write to the C:\Test.txt file and calls its Demand method to request that the runtime ensures that MyApp has the equivalent permission.
The runtime checks the permission objects assigned to MyApp and confirms that they include a FileIOPermission that represents permission to write to the C:\Test.txt file.
The runtime responds with a positive result to the File.Delete method, indicating that MyApp has the demanded permission. The positive response is not to throw a System.Security.SecurityException.
Given that MyApp has the necessary permission, the File.Delete method attempts to delete the C:\Test.txt file; only if the access controls of the underlying operating system allow the active user to delete the C:\Test.txt file will the operation succeed; we discussed the relationship between runtime and operating system security in Chapter 5.
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.
The 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.
When 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.
Some important points to note:
MethodC and MethodD have the same grant set, because they are both contained in AssemblyB and are executing from the same application domain. It is still necessary for the runtime to evaluate each method independently because of the features we discuss in Section 7.2.6.
Even though both MethodA and MethodB are contained in AssemblyA they may not have the same permissions. AssemblyA has been loaded into two different application domains. This can result in each instance having different permission sets depending on the security policy of AppDomainY; we discuss security policy in Chapter 8.
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:
Assert allows a method to specify a set of permissions for which it is willing to vouch for the callers' authority above it on the stack. The stack walk terminates with a positive result at the frame of the method invoking Assert and does not look at the remaining stack frames.
Deny is effectively the opposite of Assert. Any stack walk looking for the denied set of permissions terminates with a negative result at the frame of the method invoking Deny, regardless of the permissions held by the remaining methods on the stack.
PermitOnly is similar to Deny, but it allows a method to specify the set of permissions that should not terminate the stack walk. A security demand for any other permission results in the stack walk terminating as if it has encountered a Deny override.
We discuss each of the Assert, Deny, and PermitOnly overrides further in Section 7.2.6, where we provide implementation examples.