[ Team LiB ] Previous Section Next Section

7.2 Programming Code-Access Security

So 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 Syntax

The 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 statements

Imperative 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 statements

Declarative 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.

Because the C# and Visual Basic .NET compilers both allow you to omit the standard "Attribute" appendix from attributes when you use them in your code, it looks as if you are using the permission class which aids readability.

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

  • RequestMinimum

  • RequestOptional

  • RequestRefuse

Security demands

  • Demand

  • LinkDemand

  • InheritanceDemand

Stack walk manipulation

  • Assert

  • Deny

  • PermitOnly

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 Permissions

The 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 functionality

All 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:

System.Security.IPermission

IPermission defines methods to initiate security demands as well as to create, compare, and combine permission objects.

System.Security.IStackWalk

IStackWalk defines methods that allow code to invoke security demands and override the default stack-walk process.

System.Security.ISecurityEncodable

ISecurityEncodable defines methods that allow permission objects to be converted to and from XML representations.

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.

Table 7-3. Methods of the CodeAccessPermission class

Method

Description

IPermission interface

 

Copy

Returns a permission object that is a copy of the current permission.

Demand

Invokes a security demand for the current permission object—see Section 7.2.5 in this chapter for details.

Intersect

Returns a permission that is the intersection the current permission and the specified permission.

IsSubsetOf

Determines whether the current permission is a subset of the specified permission.

Union

Creates a permission that is the union of the current permission and the specified permission.

IStackWalk interface

 

Assert

Adds an Assert override to the current stack frame—see Section 7.2.6 in this chapter for details.

Demand

See IPermission.Demand.

Deny

Adds a Deny override to the current stack frame—see Section 7.2.6 in this chapter for details.

PermitOnly

Adds a PermitOnly override to the current stack frame—see Section 7.2.6 in this chapter for details.

FromXml

Reconstructs a permission object from a System.Security.SecurityElement that contains an XML object model representing the permissions state.

ToXml

Returns a SecurityElement that contains an XML object model of the permission object, including state information.

ISecurityEncodable

 

RevertAll

Removes all Assert, Deny, and PermitOnly overrides from the current stack frame— see Section 7.2.6 in this chapter for details.

RevertAssert

Removes all Assert overrides from the current stack frame—see Section 7.2.5 in this chapter for details.

RevertDeny

Removes all Deny overrides from the current stack frame—see Section 7.2.6 in this chapter for details.

RevertPermitOnly

Removes all PermitOnly overrides from the current stack frame—see Section 7.2.6 in this chapter for details.

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.

CodeAccessPermission does not implement IUnrestrictedPermission because identity permissions, which also extend CodeAccessPermission, never represent unrestricted states.

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:

None

Represents a completely restricted state (no permission)

Unrestricted

Represents an unrestricted state (all permissions)

7.2.2.2 Using the SecurityPermission class

The 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.

Table 7-4. Members of the SecurityPermissionFlag enumeration

Member

Description

AllFlags

All the permissions listed in this table except for NoFlags.

Assertion

The ability to call Assert on the permissions that have been granted to the code, which we discuss in Section 7.2.6.

BindingRedirects

The ability to perform binding redirection of assemblies in the application configuration file.

ControlAppDomain

The ability to create and manipulate application domains, which we discuss in Chapter 3.

ControlDomainPolicy

The ability to specify application domain policy, which we discuss in Chapter 8.

ControlEvidence

The ability to provide evidence for assemblies and application domains, which we discuss in Chapter 6.

ControlPolicy

The ability to view and edit security policy, which we discuss in Chapter 8.

ControlPrincipal

The ability to manipulate the principal object associated with application domains and threads, which we discuss in Chapter 10.

ControlThread

Advanced threading control.

Execution

The ability of code to execute.

Infrastructure

The ability to plug certain types of code extensions into the .NET Framework.

NoFlags

No permissions.

RemotingConfiguration

The ability to configure .NET Remoting types and channels.

SerializationFormatter

The ability to provide serialization services.

SkipVerification

The ability for code to skip verification, which we discuss in Chapter 4.

UnmanagedCode

The ability to execute unmanaged code. Calls to unmanaged code through either PInvoke or COM Interoperability result in an implicit security demand for the SecurityPermission.UnmanagedCode permission. This is an expensive operation when code calls unmanaged code frequently. Managed libraries that provide access to unmanaged code can use the System.Security.SuppressUnmanagedCodeAttribute to limit the performance impact; refer to the .NET Framework SDK documentation for further information.

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 Sets

When 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.

Table 7-5. Members of the PermissionSet class

Member

Description

Properties

 

Count

Gets the number of IPermission objects in the permission set.

IsReadOnly

Always returns false; read-only permission sets are not supported.

Methods

 

AddPermission

Adds an IPermission to the PermissionSet. If the PermissionSet already contains an IPermission of the same type, the result is the union of the existing IPermission and the new one. The resulting IPermission added to the PermissionSet is also returned to the caller.

Assert

Adds an Assert override to the current stack frame for all permission objects in the PermissionSet—see Section 7.2.6 in this chapter for details.

ContainsNonCodeAccessPermissions

Determines if the PermissionSet contains any IPermission objects that do not extend CodeAccessPermission.

Copy

Creates a copy of the PermissionSet.

CopyTo

Copies all of the IPermission objects in the PermissionSet to an array.

Demand

Invokes a security demand for all permission objects in the PermissionSet—see Section 7.2.5 in this chapter for details.

Deny

Adds a Deny override to the current stack frame for all permission objects in the PermissionSet—see Section 7.2.6 in this chapter for details.

FromXml

Reconstructs the state of the permission set from a System.Security.SecurityElement,which contains an XML object model representing the desired PermissionSet state.

GetEnumerator

Returns a System.Collections.IEnumerator for the IPermission objects contained in the PermissionSet.

GetPermission

Returns a copy of the IPermission of the specified System.Type contained in the PermissionSet.

Intersect

Creates a new PermissionSet that is the intersection of the current PermissionSet and another.

IsEmpty

Returns true if the PermissionSet is empty; otherwise, returns false.

IsSubsetOf

Determines if the PermissionSet is a subset of the specified PermissionSet.

IsUnrestricted

Returns true if the PermissionSet represents an unrestricted permission set; otherwise, returns false.

PermitOnly

Adds a PermitOnly override to the current stack frame for all permission objects in the PermissionSet—see Section 7.2.6 in this chapter for details.

RemovePermission

Removes the permission of the specified System.Type from the PermissionSet and returns it to the caller.

SetPermission

Adds a specified IPermission to the PermissionSet. If an IPermission of the specified type already exists, it is replaced by the new one.

ToString

Returns a System.String containing an XML representation of the PermissionSet and its contents.

ToXml

Returns a SecurityElement containing an XML object model representing the state of the PermissionSet.

Union

Creates a new PermissionSet that is the union of the current PermissionSet and another.

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:

  • PermissionSetOne, which contains a SecurityPermission representing the ability to control evidence (SecurityPermissionFlag.ControlEvidence), a FileIOPermission representing the ability to read from directory C:\ .

  • PermissionSetTwo, which contains an unrestricted SecurityPermission, and a FileIOPermission representing the ability to read andsm write to the file C:\Test.txt.

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.

Table 7-6. Named permission sets

Permission set

Description

FullTrust

Unrestricted access to all operations and resources

SkipVerification

No access to any operation or resource other than permission to skip verification, which we discuss in Chapter 4

Execution

No access to any operation or resource other than the permission to execute

Nothing

No access to any operation or resource, not even the permission to execute

LocalIntranet

A set of permissions defined in the default security policy deemed by Microsoft to be appropriate for code loaded from the local intranet

Internet

A set of permissions defined in the default security policy deemed by Microsoft to be appropriate for code loaded from the Internet

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 Requests

Earlier 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 permission

If 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 permissions

The 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 permissions

The 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 Demands

In 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 Demand

The 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 LinkDemands

A 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

Calls to the public and protected members of strong-named assemblies result in an implicit LinkDemand for the FullTrust permission set. This means that only fully trusted code can call the methods of any strong-named assembly, which is problematic for most code not run from the local machine. Applying the System.Security.AllowPartiallyTrustedCallers attribute to a strong-named assembly disables this implicit LinkDemand. See Chapter 2 for more details on using the AllowPartiallyTrustedCallers attribute.

7.2.5.3 Inheritance demands

The 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 Walks

In 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.

The .NET Framework SDK documentation states that stack-walk overrides should not be used in class constructors. The inconsistent state of the call stack during object construction may lead to undesirable results.

7.2.6.1 Assert

If 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 walk
figs/pdns_0706.gif

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 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:

  • If the assembly containing the asserting method does not have the SecurityPermission.Assertion permission

  • If the assembly containing the asserting method does not have the permission it is asserting

  • If an Assert override is already present on the callers stack frame, in which case the caller must first revert to the existing Assert

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 Deny

The 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 walk
figs/pdns_0707.gif

Assuming 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 PermitOnly

The 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 overrides

Using 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.

Table 7-7. Removing stack-walk overrides

Static method

Description

RevertAll

Causes the removal of all previous Assert, Deny, or PermitOnly overrides on the current stack frame

RevertAssert

Causes the removal of all previous Assert overrides on the current stack frame

RevertDeny

Causes the removal of all previous Deny overrides on the current stack frame

RevertPermitOnly

Causes the removal of all previous PermitOnly overrides on the current stack frame

Because the revert statements are all issued through static methods of the CodeAccessPermission class, there are no declarative syntax alternatives.

    [ Team LiB ] Previous Section Next Section