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.
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.
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.
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 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 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 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.
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
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
_
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. Permview.exe 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.
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 , 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.
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.
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 1 summarizes the methods of the
CodeAccessPermission class broken down
by interface.
Table 1. 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)
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 2 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 2. 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.
|
ControlDomainPolicy
|
The ability to specify application domain policy.
|
ControlEvidence
|
The ability to provide evidence for assemblies and application
domains.
|
ControlPolicy
|
The ability to view and edit security policy.
|
ControlPrincipal
|
The ability to manipulate the principal object associated with
application domains and threads.
|
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.
|
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 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 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. It
implements a single constructor that takes a
SecurityAction argument, which defines what type
of security action the attribute represents:
class is the attribute
counterpart of the SecurityPermission# 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 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
Unrestricted := True)> _
Assertion := True, ControlEvidence := True)> _
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 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 3 summarizes the
members of the PermissionSet class.
Table 3. 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 ; 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 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 4 creates a third permission set named
PermissionSetThree from
the union of
PermissionSetOne and
PermissionSetTwo.
Example 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:
version="1">
version="1"
Read="C:\"
Write="C:\Test.txt"/>
version="1"
Unrestricted="true"/>
If instead you had created PermissionSetThree as
the intersection of PermissionSetOne and
PermissionSetTwo using the
Intersect method, as in Example 5, the output from
PermissionSetThree.ToString would be very
different.
Example 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:
version="1">
version="1"
Read="C:\Test.txt"/>
version="1"
Flags="ControlEvidence"/>
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.
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. Table 4 lists the
possible values and gives an indication of the permissions each set
contains.
Table 4. 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
|
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
_
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\" version=\"1\">FileIOPermission, mscorlib, Version=1.0.3300.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\"/>PermissionSet>")]
# Visual Basic .NET
PermissionSet"" version=""1"">
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
_
4. Programming Permission Requests
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.
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.
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 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
Read := "C:\config")> _
All := "C:\development")> _
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.
The following RequestOptional statement requests
full access to the hard drive through an unrestricted
FileIOPermission:
# C#
[assembly:FileIOPermission(PermissionState.Unrestricted)]
# Visual Basic .NET
_
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
_
5. Programming Permission Demands
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.
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
_
Public Class SomeClass
_
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
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
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.
|
|
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
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.
6. Manipulating Stack Walks
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.
|
|
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 1
MethodB asserts an unrestricted
FileIOPermission.