[ Team LiB ] |
7.2 Programming Code-Access SecuritySo far, we have discussed permissions and introduced the features provided by CAS to enforce and control code-level security. In this section, we look at how you implement these features in your programs. Your level of interest in this section will depend on the type of code you intend to write. As a developer of applications, you will be interested in understanding the implementation and functionality of the standard permission classes, as well as how to make security requests to ensure that your application has the permissions it needs to execute. If you are writing libraries, you will also be interested in how to use permissions to control access to your functionality, as well as the features available to control the stack walk process. We begin by explaining the general syntax of security statements, followed by a look at the standard permission classes included in the .NET class library. There are more than 25 different code-access and identity permission classes in Version 1.1 of the .NET Framework, and we do not cover them all in detail. We provide detailed coverage of only the SecurityPermission class, which is an important permission for performing many of the security-related operations we discuss in this book. Once you understand how to use permissions in general, everything you need to know about specific permission classes can be found easily in the .NET Framework SDK documentation. We conclude this section by looking at how to apply different security syntax to build permission requests, security demands, and stack walk overrides into your code. 7.2.1 Security Statement SyntaxThe statements to control CAS do not form part of the C# or Visual Basic .NET languages. It is through the permission classes and their associated attribute class counterparts that you control CAS; although this makes the syntax and use of CAS more complex and initially harder to understand, it means that CAS is flexible, extensible, and to a large degree language-independent. There are two different ways to express security statements. The first, imperative security, uses the standard methods implemented by the permission classes. The second, declarative security, uses the attribute class counterparts of each permission class. 7.2.1.1 Imperative security statementsImperative security statements appear in the body of your methods and functions, which means they end up forming part of the compiled intermediate language (IL) code contained in your assembly. We discuss IL code in Chapter 2 in our discussion of assemblies. The first steps in using imperative security are to instantiate a permission object of the type you require and configure it to represent the permission you want to work with. Then you call one of the following methods, which are implemented by all code-access and identity permission classes to execute the desired security statement. For security demands, you will use the Demand method; for stack walk manipulation, you will typically use the Assert, Deny, and PermitOnly methods. We discuss the details of these methods later in this chapter; for now all we are concerned with understanding is the general syntax of imperative security statements. Example 7-1 creates a FileIOPermission object, configures it to represent write access to the file C:\SomeFile.txt, and then calls its Demand method to initiate an imperative security demand. Unless the code calling the CreateFile method (and all previous callers on the call stack) has been granted write access to the C:\SomeFile.txt file, the runtime throws a System.Security.SecurityException. Example 7-1. Imperative security statement# C# public void CreateFile( ) { // Create a new FileIO permission object FileIOPermission perm = new FileIOPermission( FileIOPermissionAccess.Write, @"C:\SomeFile.txt"); try { // Demand the FileIOPermission perm.Demand( ); } catch (SecurityException se) { // Callers do not have necessary permission } // Method implementation... } # Visual Basic .NET Public Function CreateFile ( ) ' Create a new FileIO permission object Dim perm As FileIOPermission = New FileIOPermission( _ FileIOPermissionAccess.Write, "C:\SomeFile.txt") Try ' Demand the FileIOPermission perm.Demand( ) Catch (se As SecurityException) ' Callers do not have necessary permission End Try ' Method implementation... End Function You cannot express all security operations using imperative syntax. However, imperative security offers a level of flexibility and control that you cannot achieve using declarative syntax, which we discuss in Section 7.2.1.2. Because imperative security statements appear in the body of your program, you can use them in conjunction with the normal program control constructs supported in the language, such as conditional and iterative statements. In addition, imperative security allows you to base security decisions on information that is available only at runtime, such as user input, and you can use structured exception handling to process security exceptions that result from your imperative security statements. 7.2.1.2 Declarative security statementsDeclarative security statements are expressed using attributes, which are statements compiled to form part of an assembly's metadata. For every code-access and identity permission class, the .NET class library contains a corresponding attribute class, which enables the use of declarative security syntax with that permission. Each attribute class is a member of the same namespace as its corresponding permission class and has the same name as the permission, but with the word "Attribute" appended. For example, the corresponding attribute for the FileIOPermission class is the FileIOPermissionAttribute class, and they are both members of the System.Security.Permissions namespace.
All permission attribute classes extend the System.Security.Permissions.CodeAccessSecurityAttribute class, including the attribute class counterparts of identity permissions. The CodeAccessSecurityAttribute class and every subclass implement a single constructor with the signature shown here: # C# public CodeAccessPermissionAttribute( SecurityAction action ); # Visual Basic .NET Public Sub New( _ ByVal action As SecurityAction _ ) System.Security.Permissions.SecurityAction is an enumeration that contains members representing each of the security operations supported by declarative syntax. Permission requests
Security demands
Stack walk manipulation
The selection of operations supported by declarative security is more than that supported using imperative security. We discuss the details of each operation later in this chapter; for now we are concerned only with the general syntax of declarative security statements. Each attribute class defines a set of properties that are specific to the type of permission. These allow you to configure the attribute to represent the permission state to which you want to invoke the declarative security statement. The following code demonstrates the declarative syntax alternative to that listed in Example 7-1. The Write property is specific to the FileIOPermissionAttribute class and specifies the full path of the file to which write access is granted, as in C:\SomeFile.txt. To change the security operation, it is a simple matter of changing the value of the SecurityAction argument: # C# [FileIOPermission(SecurityAction.Demand, Write = @"C:\SomeFile.txt"] public void CreateFile( ) { // Method implementation... } # Visual Basic .NET <FileIOPermission(SecurityAction.Demand, Write = "C:\SomeFile.txt"> _ Public Sub CreateFile ( ) ' Method implementation... End Sub As metadata, the runtime and other tools can extract and view the declarative security statements contained in your assembly without loading or running the assembly's IL. Permview.exe is one such tool supplied with the .NET Framework. We discuss the use of Permview.exe in Chapter 9, but in summary, it allows security administrators to inspect all the declarative security statements contained in your assembly. This provides them with valuable information when they are configuring security policy. The primary limitation of declarative syntax when compared with imperative syntax is that the permission definitions are fixed at build time and cannot be based on runtime state. The only way to change the permissions is to modify the code and recompile it. 7.2.2 Programming PermissionsThe use you make of the standard permission classes in your own development will vary depending on what type of code you are writing. If you are writing applications that call protected functionality of the .NET class library, you will be concerned primarily with ensuring that your code has the necessary permissions to call the functionality it needs and fails gracefully if it does not. Your focus will be on the correct configuration of security policy (see Chapter 8), making the necessary permission requests and correctly handling any security exceptions thrown at runtime. If you are writing library code that will be used by others, use the standard evidence classes in the same way that the .NET class library does to protect the functionality you provide. Be aware that if you are providing a wrapper around existing .NET class library functionality, you may not need to implement your own security because the underlying methods you call could already be protected. If you are creating a library that calls unmanaged code or provides access to a new type of resource, you may decide to protect it using an appropriate standard permission class. However, permission classes tend to be task-specific, and you might find yourself wanting to implement a completely new permission class to represent your needs, which we discuss in Section 7.3.1. Each of the standard permission classes represents a different type of secured functionality or code identity. Some code-access permission classes represent access to a set of discrete actions grouped together into a single container (for example, SecurityPermission), but many represent access to an infinite variety of resources (like FileIOPermission). Despite this variety and scope of purpose, all permission classes integrate with the security framework to provide the same base functionality and pattern of use. 7.2.2.1 Common permission class functionalityAll code-access and identity permissions extend directly or indirectly the abstract System.Security.CodeAccessPermission class, which implements three interfaces that define the functionality all permission classes must provide:
In addition to the methods defined by these interfaces, CodeAccessPermission implements four static methods that are used to undo previously implemented stack-walk overrides. Table 7-3 summarizes the methods of the CodeAccessPermission class broken down by interface.
All code-access permission classes also implement the System.Security.Permissions.IUnrestrictedPermission interface, which includes a single method named IsUnrestricted. The IsUnrestricted method returns true if a permission object represents the unrestricted state of the permission class; otherwise, it returns false.
Each concrete code-access permission class implements its own internal state to represent the functionality to which it controls access. Likewise, each concrete identity permission class implements internal state to represent values appropriate to the type of evidence on which they are based. Therefore, all permission classes implement a unique set of constructors, properties, and methods necessary to manipulate their own internal state information. One constructor signature common to all code-access permission classes is shown here for the SecurityPermission class: # C# public SecurityPermission( PermissionState state ); # Visual Basic .NET Public Sub New( _ ByVal state As PermissionState _ ) The PermissionState enumeration contains two member values:
7.2.2.2 Using the SecurityPermission classThe SecurtityPermission class is used by both the runtime and methods in the .NET Framework class library to control access to a set of 14 discrete operations that are critical to the security of the runtime environment. Many of the activities we discuss in this book require permissions represented by the SecurtityPermission class. These include things like controlling evidence, modifying security policy, and creation application domains. The SecurityPermission class implements two constructors with the following signatures: # C# public SecurityPermission( PermissionState state ); public SecurityPermission( SecurityPermissionFlag flag ); # Visual Basic .NET Public Sub New( _ ByVal state As PermissionState _ ) Public Sub New( _ ByVal flag As SecurityPermissionFlag _ ) The first constructor takes a member of the PermissionState enumeration, which we discussed in the previous section. Passing the value PemissionState.None creates a SecurityPermission that grants access to none of the contained 14 operations, while PemissionState.Unrestricted grants access to them all. The second constructor takes a member of the System.Security.Permissions.SecurityPermissionFlag enumeration, which defines the set of component operations represented by the SecurityPermission object. Table 7-4 lists the members of the SecurityPermissionFlag enumeration. Because SecurityPermissionFlag has the FlagsAttribute attribute, you can specify more than one value simultaneously using bitwise combination.
Example 7-2 demonstrates how to create two SecurityPermission objects. The first represents unrestricted access to all SecurityPermission operations, and the second specifies only the Assertion and ControlEvidence permissions. Example 7-2. Constructing SecurityPermission objects# C# SecurityPermission p1 = new SecurityPermission(PermissionState.Unrestricted); SecurityPermission p2 = new SecurityPermission( SecurityPermissionFlag.Assertion | SecurityPermissionFlag.ControlEvidence ); # Visual Basic .NET Dim p1 As SecurityPermission = _ New SecurityPermission(PermissionState.Unrestricted) Dim p2 As SecurityPermission = _ New SecurityPermission(SecurityPermissionFlag.Assertion Or _ SecurityPermissionFlag.ControlEvidence) Using these SecurityPermission objects, you can execute imperative security statements. For example, your code could use an imperative security demand to control the functionality it exposes to the calling code based on the permissions the callers have: # C# // Declare boolean on which to base available functionality bool unrestricted = false; // Test to see if callers have unrestricted security access try { p1.Demand( ); unrestricted = true; } catch (SecurityException) { // Don't have unrestricted access, check for Assertion and // ControlEvidence try { p2.Demand( ); } catch (SecurityException) { // No Assertion or ControlEvidence permisson either // Take appropriate action } } # Visual Basic.NET ' Declare boolean on which to base available functionality Dim unrestricted As Boolean = False ' Test to see if callers have unrestricted security access Try p1.Demand( ) unrestricted = True Catch ' Don't have unrestricted access, check for Assertion and ' ControlEvidence Try p2.Demand( ) Catch ' No Assertion or ControlEvidence permisson either ' Take appropriate action End Try End Try Given the nature of the actions protected by SecurityPermission, it is more likely that you will want to use it in a permission request. The SecurityPermissionAttribute class is the attribute counterpart of the SecurityPermission class. It implements a single constructor that takes a SecurityAction argument, which defines what type of security action the attribute represents: # C# public SecurityPermissionAttribute( SecurityAction action ); # Visual Basic .NET Public Sub New( _ ByVal action As SecurityAction _ ) The configuration of SecurityPermissionAttribute is specified using its properties. SecurityPermissionAttribute defines a set of properties with the same name and purpose as the component permissions listed in Table 7-4. Each of these properties takes a Boolean value to indicate whether the attribute configuration contains the specified component permission. There is also an Unrestricted property, which allows you to represent an unrestricted SecurityPermission, and a Flags attribute, which allows you to logically combine the values of SecurityPermissionFlag listed in Table 7-4, as you would in a normal SecurityPermission constructor. The following code demonstrates the use of the SecurityPermissionAttribute to define the equivalent SecurityPermisson instances as shown in Example 7-2. In this case, we are issuing a minimum-permission request for the specified SecurityPermission, as indicated by the SecurityAction.RequestMinimum argument: # C# [SecurityPermission(SecurityAction.RequestMinimum, Unrestricted = true)] [SecurityPermission(SecurityAction. RequestMinimum, Assertion = true, ControlEvidence = true)] # Visual Basic .NET <SecurityPermission(SecurityAction. RequestMinimum, _ Unrestricted := True)> _ <SecurityPermission(SecurityAction. RequestMinimum, _ Assertion := True, ControlEvidence := True)> _ 7.2.3 Programming Permission SetsWhen implementing CAS features in your code, you will often need to work with more than one permission at a time. For example, a method can make a security demand for both FileIOPermission and SecurityPermission. The System.Security.PermissionSet class provides the capability to create permission sets that can contain an arbitrary number of individual permission objects. Permission sets expose much of the same functionality as the individual permission classes, and you can use an entire permission set in the same way as you can an individual permission to enforce code-level security. The PermissionSet class implements two constructors with the following signatures: # C# public PermissionSet( PermissionSet permSet ); public PermissionSet( PermissionState state ); # Visual Basic .NET Public Sub New( _ ByVal permSet As PermissionSet _ ) Public Sub New( _ ByVal state As PermissionState _ ) The first constructor populates the new PermissionSet with copies of the permission objects contained in the specified PermissionSet. The second constructor takes a member of the PermissionState enumeration, which we discussed in Section 7.2.2.1. Specifying PermissionState.None creates an empty PermissionSet with no permissions. Passing PermissionState.Unrestricted creates a permission set that is equivalent to having all permissions that implement the IUnrestricted interface (this includes all of the standard code-access permissions but not the identity permissions). PermissionSet implements the same IStackWalk interface that the individual permission classes do, which allows you to execute the Assert, Demand, Deny, and PermitOnly methods against the entire set of permissions with a single call. PermissionSet also implements the System.Collections.ICollection and System.Collections.IEnumerator interfaces to facilitate the management and manipulation of the contained permission objects. Table 7-5 summarizes the members of the PermissionSet class.
The runtime uses permission sets extensively during policy resolution (which we discuss in Chapter 8); you will find this functionality invaluable as you work more with permissions. Example 7-3 creates and populates two PermissionSet objects:
Example 7-3. Creating a permission set from an intersection of permission sets# C# // Create and populate PermissionSetOne PermissionSet PermissionSetOne = new PermissionSet(PermissionState.None); PermissionSetOne.AddPermission( new SecurityPermission(SecurityPermissionFlag.ControlEvidence)); PermissionSetOne.AddPermission( new FileIOPermission(FileIOPermissionAccess.Read, @"C:\")); // Create and populate PermissionSetTwo PermissionSet PermissionSetTwo = new PermissionSet(PermissionState.None); PermissionSetTwo.AddPermission( new SecurityPermission(PermissionState.Unrestricted)); PermissionSetTwo.AddPermission( new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, @"C:\Test.txt")); # Visual Basic .NET ' Create and populate PermissionSetOne Dim PermissionSetOne As PermissionSet = _ New PermissionSet(PermissionState.None) PermissionSetOne.AddPermission( _ New SecurityPermission(SecurityPermissionFlag.ControlEvidence)) PermissionSetOne.AddPermission( _ New FileIOPermission(FileIOPermissionAccess.Read,"C:\")) ' Create and populate PermissionSetTwo Dim PermissionSetTwo As PermissionSet = _ New PermissionSet(PermissionState.None) PermissionSetTwo.AddPermission( _ New SecurityPermission(PermissionState.Unrestricted)) PermissionSetTwo.AddPermission( _ New FileIOPermission(FileIOPermissionAccess.Read _ Or FileIOPermissionAccess.Write, "C:\Test.txt")) Example 7-4 creates a third permission set named PermissionSetThree from the union of PermissionSetOne and PermissionSetTwo. Example 7-4. Creating a permission set from a union of permission sets# C# // Create PermissionSetThree PermissionSet PermissionSetThree = PermissionSetOne.Union(PermissionSetTwo); # Visual Basic .NET ' Create PermissionSetThree Dim PermissionSetThree As PermissionSet = _ PermissionSetOne.Union(PermissionSetTwo) Calling PermissionSetThree.ToString produces the following output, which, incidentally, is the same as calling PermissionSetThree.ToXml.ToString. You can see that the resulting FileIOPermission lists both of the permissions we specified, whereas the SecurityPermission shows an unrestricted state, which includes the ControlEvidence ability: <PermissionSet class="System.Security.PermissionSet" version="1"> <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="C:\" Write="C:\Test.txt"/> <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/> </PermissionSet> If instead you had created PermissionSetThree as the intersection of PermissionSetOne and PermissionSetTwo using the Intersect method, as in Example 7-5, the output from PermissionSetThree.ToString would be very different. Example 7-5. Creating a permission set from an intersection of permission sets# C# // Create PermissionSetThree PermissionSet PermissionSetThree = PermissionSetOne.Intersect(PermissionSetTwo); # Visual Basic .NET ' Create PermissionSetThree Dim PermissionSetThree As PermissionSet = _ PermissionSetOne.Intersect(PermissionSetTwo) Notice that FileIOPermission now includes only the ability to Read from C:\Test.txt and not to write to it. Also, you are left only with the ability to implement ControlEvidence from the SecurityPermission: <PermissionSet class="System.Security.PermissionSet" version="1"> <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="C:\Test.txt"/> <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="ControlEvidence"/> </PermissionSet> When creating the union and intersection of permission sets, the PermissionSet object relies on the Union and Intersect methods of the individual IPermission objects. The logic used by each IPermission class to calculate unions and intersections is class-dependent; consult the .NET Framework SDK documentation for details. The NamedPermissionSet class extends PermissionSet but includes only two additional members: the Name property gets and sets a name for the NamedPermissionSet, and the Description property gets and sets the description for the NamedPermissionSet. You will use NamedPermissionSet objects primarily when working with security policy, which we discuss in Chapter 8. The System.Security.Permissions.PermissionSetAttribute class enables you to use permission sets with declarative security syntax. As with permission attributes, you must use the values of the SecurityAction enumeration to specify the security statement you want to invoke. To specify the contents of the permission set, you use the Name, XML, and File properties of PermissionSetAttribute. The Name property allows you to specify the name of one of the immutable named permission sets included in .NET's default security policy, which we discuss in Chapter 9. Table 7-6 lists the possible values and gives an indication of the permissions each set contains.
The following example demonstrates a declarative security demand using the Internet named permission set: # C# [assembly:PermissionSet(SecurityAction.Demand,Name = "Internet")] # Visual Basic .NET <assembly: PermissionSet(SecurityAction. Demand,Name := "Internet")> _ The XML property allows you to provide a String containing an XML representation of the permission set you want to use. The correct format of the XML can be seen using PermissionSet.ToString, which we did earlier. The following example executes a declarative security demand on a permission set containing an unrestricted FileIOPermission. Although you cannot show it correctly here because of the length of the text, you should not have line breaks in the middle of the quoted strings within the XML text: # C# [PermissionSet(SecurityAction.Demand, XML = "<PermissionSet class=\"System.Security. PermissionSet\" version=\"1\"><IPermission class=\"System.Security.Permissions. FileIOPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\"/></ PermissionSet>")] # Visual Basic .NET <PermissionSet(SecurityAction.Demand, XML := "<PermissionSet class=""System.Security. PermissionSet"" version=""1""><IPermission class=""System.Security.Permissions. FileIOPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" version=""1"" Unrestricted=""true""/></ PermissionSet>")> _ Notice that the use of the XML property can be a little unwieldy even for small permission sets. The File property allows you to specify the name of a file that contains the XML representation of the permission set. The following example uses the contents of the Test.xml file from the local directory to execute a declarative security demand. The Test.xml file content is used during compilation of your code but is not required afterwards: # C# [PermissionSet(SecurityAction.Demand, File = "Test.xml")] # Visual Basic .NET <PermissionSet(SecurityAction.Demand, File = "Test.xml")> _ 7.2.4 Programming Permission RequestsEarlier in Section 7.1.2, we explained that CAS supports three types of permission requests, which are represented by the following members of the SecurityAction enumeration: RequestMinimum, RequestOptional, and RequestRefuse. Permission requests are evaluated as the runtime loads an assembly and are expressed only using declarative security syntax. Because permission requests define the security requirements of your assembly, they must be declared using assembly scope attributes. Each statement takes a permission, or set of permissions, that can include any of the standard code-access and identity permission classes. After the runtime calculates the grant set of an assembly, it looks at any permission request statements in the assembly and applies them, as described in the following sections. 7.2.4.1 Requesting minimum permissionIf you are writing code that must have access to a certain set of permissions in order to function correctly, the best tactic is to configure minimum-permission requests in your assembly. This is especially true if your code requires permissions that are not normally granted under default security policy. For example, if your code writes to the Windows registry but you expect people to run it over the Internet, many people will have problems running your code, because default Internet policy does not give access to write to the registry. We discuss the permission granted by the default security policy in Chapter 9. By including a RequestMinimum statement in your assembly, you can specify that your code requires permission to write to the registry. This will not alter the fact that your code does not have the permission, but it will stop your application from loading; the runtime will throw a System.Security.Policy.PolicyExcpetion and identify the permission that is required. Knowing that there is a problem, the user or security administrator can view the assembly's permission requests with the Permview.exe tool (see Chapter 9) and decide whether to make the necessary security policy changes. The use of RequestMinimum statements also allows you to simplify your code. Because the runtime will not load your assembly if it does not have the specified minimum set of permissions, you do not need to place SecurityException handling code around every secured operation you perform. The fact that the code is running is proof that it has the required permissions. The following code demonstrates how to declare a minimum-permission request for the FileIOPermission to read from the C:\config directory and have full access to the C:\development directory. Note that you cannot include both the Read and All properties in a single statement; the FileIOPermissionAttribute requires that you use a separate attribute for each path requested: # C# [assembly:FileIOPermission(SecurityAction.RequestMinimum, Read = @"C:\config")] [assembly:FileIOPermission(SecurityAction.RequestMinimum, All = @"C:\development")] # Visual Basic .NET <assembly:FileIOPermission(SecurityAction.RequestMinimum, _ Read := "C:\config")> _ <assembly:FileIOPermission(SecurityAction.RequestMinimum, _ All := "C:\development")> _ 7.2.4.2 Requesting optional permissionsThe RequestOptional statement specifies the maximum set of permissions that the runtime should grant to your assembly regardless of security policy configuration. The runtime will still load your assembly if it cannot grant all of the permissions specified in the RequestOptional statement, but it will never grant more than those permissions specified. The RequestOptional statement serves two principal purposes. First, you can request permissions that your application will make use of if they are granted. This could be to support noncore functionality that is made available only if the runtime grants the necessary permissions. Second, RequestOptional statements provide an excellent mechanism to limit the security exposure of your code. By minimizing the functionality and resources to which your code has access, you can ensure that bugs in your code cannot inadvertently destroy or modify protected resources. In addition, you reduce the chance that malicious users can use your code as a gateway to execute protected operations surreptitiously. The major problem with optional permission requests is that the runtime does not indicate to the assembly which (if any) of the optional permissions requested it actually granted. You must either use exception handling around the code where you rely on the optional permissions or test whether the assembly has each of the permissions using the SecurityManager.IsGranted method, which we discuss in Chapter 8. The following RequestOptional statement requests full access to the hard drive through an unrestricted FileIOPermission: # C# [assembly:FileIOPermission(PermissionState.Unrestricted)] # Visual Basic .NET <assembly:FileIOPermission(PermissionState.Unrestricted)> _ 7.2.4.3 Refusing permissionsThe RequestOptional statement enables you to define an upper limit on the permission set that the runtime will grant to your assembly. However, if there is a specific permission or set of permissions you are concerned about, then it is easy to ensure that they are never granted to your assembly using a RequestRefuse statement. Even if the runtime would have normally granted the permissions to the assembly based on policy resolution, the permission will be removed if specified in the RequestRefuse statement: The following RequestRefuse statement refuses all access to the hard drive using an unrestricted FileIOPermission: # C# [assembly:FileIOPermission(PermissionState.Unrestricted)] # Visual Basic .NET <assembly:FileIOPermission(PermissionState.Unrestricted)> _ 7.2.5 Programming Permission DemandsIn Section 7.1.4, we discussed the basic security demand Demand, which is the most commonly used mechanism of enforcing code-access security. In addition, CAS supports two other kinds of security demands: LinkDemand and InheritanceDemand You will use LinkDemand and InheritanceDemand less frequently than Demand, but under certain circumstances, they provide invaluable mechanisms to protect your code from misuse. In the following sections, we describe the purpose of the LinkDemand and InheritanceDemand statements and demonstrate how to use them. First, we complete our coverage of Demand statements. 7.2.5.1 DemandThe Demand statement causes the runtime to walk the stack and ensures that not only the immediate caller, but all previous callers have a set of demanded permissions. You can use either declarative or imperative syntax to invoke a Demand. A declarative Demand occurs as soon as code invokes the protected method, whereas an imperative Demand occurs where you position the statement in your code. You can apply declarative demands to classes or individual functional members, such as methods, properties, and events. If you use declarative syntax to specify a Demand on a class declaration, the same Demand is enforced on every class member. To override a class-level Demand, simply specify a different Demand on the individual members. The following code demonstrates the use and syntax of the Demand statement. We have applied a declarative Demand for the "Internet" permission set to the SomeClass class, but MethodA overrides this Demand with its own declarative Demand for an unrestricted FileIOPermission. The class-level Demand is still enforced on MethodB, but within the body of MethodB, we make an additional imperative Demand for an unrestricted FileIOPermission: # C# using System.Security; using System.Security.Permissions; [PermissionSet(SecurityAction.Demand, Name = "Internet")] public class SomeClass { [FileIOPermission(SecurityAction.Demand, Unrestricted = true)] public void MethodA( ) { //... } public void MethodB( ) { // Create a new unrestricted FileIOPermission object FileIOPermission f = new FileIOPermission(PermissionState.Unrestricted); try { // Invoke security demand f.Demand( ); } catch (SecurityException se) { // Handle exception condition } } } # Visual Basic .NET Imports System.Security Imports System.Security.Permissions <PermissionSet(SecurityAction.Demand, Name := "Internet")> _ Public Class SomeClass <FileIOPermission(SecurityAction.Demand, Unrestricted := True)> _ Public Sub MethodA( ) '... End Sub Public Sub MethodB( ) ' Create a new unrestricted FileIOPermission object Dim f As FileIOPermission = _ New FileIOPermission(PermissionState.Unrestricted) Try ' Invoke security demand f.Demand( ) Catch se As SecurityException ' Handle exception condition End Try End Sub End Class 7.2.5.2 LinkDemandsA LinkDemand is similar to a Demand, but it is evaluated only when a caller first links to your code and only checks the permissions of the immediate caller as opposed to the permissions of all callers on the call stack. LinkDemand provides a lightweight alternative to the Demand and is used to reduce the performance hit of performing stack walks on frequently called and time-critical methods. However, because the permissions of all callers are not checked, LinkDemand leaves your code susceptible to attacks. Because the runtime resolves LinkDemand security before your code is running, you can only invoke LinkDemand statements using declarative syntax. Like the Demand, if you apply a LinkDemand to a class declaration, it is enforced on all class members. If you also apply LinkDemand statements to individual class members, the runtime evaluates the class- and member-level LinkDemand statements sequentially. The following code demonstrates the syntax of LinkDemand to allow only code loaded from the MyComputer Internet Explorer Zone to call method MethodA: # C# [ZoneIdentityPermission(SecurityAction.LinkDemand, Zone = SecurityZone.MyComputer)] public void MethodA( ) { // ... } # Visual Basic .NET <ZoneIdentityPermission(SecurityAction.LinkDemand, _ Zone := SecurityZone.MyComputer)> _ Public Sub MethodA( ) ' ... End Sub
7.2.5.3 Inheritance demandsThe InheritanceDemand statement allows you to restrict which code can extend your classes. For example, you may want to allow only code that is signed with your company's publisher certificate to extend a set of business-critical classes. In this case, apply the following InheritanceDemand to your class definition (assuming certificate.cer contains your company's publisher certificate): # C# [PublisherIdentityPermission(SecurityAction.InheritanceDemand, CertFile = "certificate.cer")] public class SomeClass { //... } # Visual Basic .NET <PublisherIdentityPermission(SecurityAction.InheritanceDemand, _ CertFile := "certificate.cer")> _ Public Class SomeClass '... End Class The runtime enforces the InheritanceDemand when it loads an assembly containing a class that extends the protected class. If the loaded assembly does not have the demanded permission (in this case, the necessary PublisherIdentityPermission), the runtime will throw a SecurityException. You can also apply an InheritanceDemand to individual methods, properties, and events to define the permissions code it must have if it wants to override the protected member. Whether applied at the class or member level, the InheritanceDemand is transitive—all classes derived directly or indirectly from the protected class must meet the requirements specified in the InheritanceDemand. 7.2.6 Manipulating Stack WalksIn Section 7.1.4.1, we described how the runtime uses the call stack in response to a security demand to ensure that every caller on the stack has the demanded permission or set of permissions. This is normally desirable, but in some instances, you will need to use stack-walk overrides to change this default behavior. CAS supports three different stack-walk overrides: Assert, Deny, and PermitOnly. You can use either declarative or imperative syntax to invoke these overrides, and can specify either individual permissions or a permission set. Each statement causes the runtime to annotate the stack frame of the current method with the type of override and the set of permissions to which it applies. In the course of execution, when a stack walk occurs, the runtime uses the annotation information to modify the default stack walk behavior. If a single stack frame has different types of overrides in effect, the runtime applies the PermitOnly, then the Deny, and finally the Assert. There can be only one of each type of override in effect on each stack frame. Before adding a new override of the same type, you must revert the existing override or the runtime throws a SecurityException; see Section 7.2.6.4 later in this section for details. When using declarative syntax to express Assert, Deny, and PermitOnly operations, apply them at the class, method, or property level. If you specify the security demand at the class level, the same override is applied to every member of the class. Applying a different override to an individual method will replace the default class-level override.
7.2.6.1 AssertIf you write code that accesses a protected resource or action, it is unreasonable to expect that security policy will grant all code that uses your code the same level of permission. Assert allows a method to override the normal stack-walk process and vouch for the authority of the callers above it on the stack. During a stack walk, if the demanded permission matches or is a subset of the asserted permissions, then the stack walk terminates successfully at the stack frame of the method that made the assertion. For example, in Figure 7-6 MethodB asserts an unrestricted FileIOPermission. Figure 7-6. The effect of Assert on a stack walkWhen MethodD calls the static System.IO.File.Delete method to delete the C:\Test.txt file, Delete makes a security demand for the permission to write to C:\Test.txt, which is similar to the following code: # C# FileIOPermission p = new FileIOPermission(FileIOPermissionAccess.Write,@"C:\Test.txt"); p.Demand( ); # Visual Basic .NET Dim p As New FileIOPermission(FileIOPermissionAccess.Write,"C:\Test.txt") p.Demand( ) Assuming that both MethodD and MethodC have the necessary permission, when the stack walk reaches the stack frame for MethodB, it compares the demanded FileIOPermission with the asserted unrestricted FileIOPermission. Because permission to write to the C:\Test.txt file is a subset of the unrestricted FileIOPermission permission, the stack walk terminates as if all callers on the call stack had been found to have the demanded permission. The true grant sets of AppDomainY, MethodA, and AppDomainX are irrelevant. If the security demand that initiated the stack walk was called on a permission set and the asserted permissions satisfy only part of the security demand, the stack walk will continue, but the remaining stack frames will only be compared against the subset of permissions not satisfied by the Assert. The following code shows the imperative security statement to Assert the unrestricted FileIOPermission described in this example: # C# public void MethodB( ) { try { // Instantiate and Assert FileIOPermission FileIOPermission p = new FileIOPermission( PermissionState.Unrestricted); p.Assert( ); } catch (SecurityException se) { // Handle SecurityException } } # Visual Basic .NET Public Sub MethodB( ) Try ' Instantiate and Assert FileIOPermission Dim p As FileIOPermission = _ New FileIOPermission(PermissionState.Unrestricted) p.Assert( ) Catch se As SecurityException ' Handle SecurityException End Try End Sub Note that the Assert call can throw a System.Security.SecurityException in three instances:
The following code shows the equivalent declarative syntax to Assert an unrestricted FileIOPermission: # C# [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] public void MethodB( ) {} # Visual Basic .NET <FileIOPermission(SecurityAction.Assert, Unrestricted := True)> _ Public Sub MethodB( ) End Sub Before using Assert, you should be confident that your code cannot be used as a gateway through which malicious code can access protected resources. This is particularly true because the Assert override is frequently used with highly trusted permissions not normally given to all applications, such as the ability to execute unmanaged code. Used carelessly, Assert can open up gaping holes in the overall security of the runtime. 7.2.6.2 DenyThe Deny is the opposite of an Assert. Instead of vouching for the authority of the callers higher on the call stack and terminating a stack walk with a positive response, the runtime throws a SecurityException to the code that initiated the Demand and terminates the stack walk. For example, in Figure 7-7 MethodB denies an unrestricted FileIOPermission. This causes any stack walk looking for access to the hard drive to fail. When MethodD calls the static System.IO.File.Delete method to delete the C:\Test.txt file, Delete makes a security demand for the permission to write to the C:\Test.txt file. Figure 7-7. The effect of Deny on a stack walkAssuming the stack walk reaches MethodB, it compares the demanded FileIOPermission with the denied unrestricted FileIOPermission. Because permission to write to C:\Test.txt file is a subset of the unrestricted FileIOPermission permission, the stack walk terminates and throws a SecurityException in the Delete method. The following code shows the imperative security syntax to Deny the unrestricted FileIOPermission described in the example. Note that the Deny call can throw a SecurityException if there is already a Deny override in place for the current stack frame: # C# public void MethodB( ) { try { // Instantiate and Deny FileIOPermission FileIOPermission p = new FileIOPermission( PermissionState.Unrestricted); p.Deny( ); } catch (SecurityException se) { // Handle SecurityException } } # Visual Basic .NET Public Sub MethodB( ) Try ' Instantiate and Deny FileIOPermission Dim p As FileIOPermission = _ New FileIOPermission(PermissionState.Unrestricted) p.Deny( ) Catch se As SecurityException ' Handle SecurityException End Try End Sub The following example shows the equivalent declarative syntax to Deny an unrestricted FileIOPermission: # C# [FileIOPermission(SecurityAction.Deny, Unrestricted = true)] public void MethodB( ) {} # Visual Basic .NET <FileIOPermission(SecurityAction.Deny, Unrestricted := True)> _ Public Sub MethodB( ) End Sub 7.2.6.3 PermitOnlyThe PermitOnly stack-walk override is like an inverted Deny. Instead of specifying the permissions that should be denied, you specify only those permissions that permit the stack walk to continue unaffected. This provides a more concise way of denying large sets of permissions without the need to specify them all in a Deny override. In the following code, MethodB will cause a stack walk to terminate (and a SecurityException to be thrown) if methods further down the call stack demand any permission other than a FileIOPermission. In this instance, the stack walk illustrated in Figure 7-7 passes through the overrides of MethodB without effect, because the permission to write to the C:\Test.txt file is a subset of the unrestricted FileIOPermission permission: # C# public void MethodB( ) { try { // Instantiate and PermitOnly FileIOPermission FileIOPermission p = new FileIOPermission( PermissionState.Unrestricted); p.PermitOnly( ); } catch (SecurityException se) { // Handle SecurityException } } # Visual Basic .NET Public Sub MethodB( ) Try ' Instantiate and PermitOnly FileIOPermission Dim p As FileIOPermission = _ New FileIOPermission(PermissionState.Unrestricted) p.PermitOnly ( ) Catch se As SecurityException ' Handle SecurityException End Try End Sub The following example shows the equivalent declarative syntax to PermitOnly an unrestricted FileIOPermission: # C# [FileIOPermission(SecurityAction.PermitOnly, Unrestricted = true)] public void MethodB( ) {} # Visual Basic .NET <FileIOPermission(SecurityAction.PermitOnly, Unrestricted := True)> _ Public Sub MethodB( ) End Sub 7.2.6.4 Reverting overridesUsing the static methods of the CodeAccessPermission class listed in Table 7-7, code removes the stack frame annotations made by previous calls to Assert, Deny, and PermitOnly. If you call any of these methods and there is none of the specified override in effect, the runtime throws a System.ExecutionEngineException that you must catch and handle appropriately.
Because the revert statements are all issued through static methods of the CodeAccessPermission class, there are no declarative syntax alternatives. |
[ Team LiB ] |