Coding with CAS

So far we've learned a lot about the purpose of CAS and the available permissions. However, apart from a few code snippets, we've not seen too much in the way of code examples. There's a good reason for that - the CLR's security architecture is very well designed to offer protection for code behind the scenes. That means that, while it's important to understand how CAS works and how it might affect your code, in practice it's not that often that you'll have to invoke the security infrastructure explicitly from your code. The places where you might have had to to do that have largely already been dealt with by Microsoft, in the implementations of the framework base classes. There are, however, a few occasions when you will need to deal with security from your own code, for example to make an assert or demand. This usually happens if you are writing code that accesses some resources that should be protected, and you need to ensure that your assemblies cannot be misused by other assemblies. You may also wish to use security-related attributes to publicly declare to the world (via reflection) what permissions your assembly is going to need to execute. In the next few pages, I'll go over the code you'd need to write to perform operations like that.

Imperative Security

Imperative security is the name given to the process of actively demanding a security permission (or taking some other action using a security permission) inside the code for a method. All the code snippets we've seen so far involve imperative security.

If you want to use imperative security, the first thing you'll need to do is to instantiate one of the CodeAccessPermission-derived classes. For example, to instantiate an object that represents the FileDialog permission to open (but not save) a file selected by the user in an open file dialog:

 FileDialogPermission dlgPerm = new                      FileDialogPermission(FileDialogPermissionAccess.Open); 

You'll need to check in the documentation what the parameters to be supplied to the constructor of the permission you want are, since that depends entirely on how that permission can be broken into subpermissions. Some, such as FileDialogPermission and SecurityPermission, have [Flag] enumerations to describe the subpermissions. FileIOPermission, on the other hand, can take a string indicating the file path and an enumeration indicating the kind of access required.

We've already seen examples of how code demands a permission, by calling CodeAccessPermission.Demand(). The other main actions you can take are supplied by three other methods defined by CodeAccessPermission:Assert(), Deny(), and PermitOnly().

For example, suppose we wish to demand that this assembly and all callers have permission to both skip verification and execute unmanaged code:

 SecurityPermission secPerm = new SecurityPermission(                                     SecurityPermissionFlag.SkipVerification |                                     SecurityPermissionFlag.UnmanagedCode); secPerm.Demand(); 

Similarly, to assert that callers need not have this permission:

 // secPerm defined as above secPerm.Assert(); 

To deny any future requests for this permission:

 // secPerm defined as above secPerm.Deny(); 

And to ensure this is the only permission granted from now on:

 // secPerm defined as above secPerm.PermitOnly(); 

There is a subtle difference between demanding a permission and the other operations: if you demand a permission, that is a one-off action which may or may or not throw an exception. All a demand does is effectively to say to the CLR, "please throw a security exception if this permission is not available to me, given the current state of the call stack". However, the other operations are all designed rather to influence the outcome of future demands.

Each of these methods places what is known as a stack walk modifier on the call stack information for the current method. This stack walk modifier will be removed as soon as the current method exits, so it's not possible for a call to Assert(), Deny(), or PermitOnly() to have any effect beyond the method in which it is placed. There is one restriction on the use of stack modifiers: it is not possible to place more than one modifier of the same type simultaneously in the same method. For example, the following code will throw an exception:

 SecurityPermission secPerm = new SecurityPermission(                                     SecurityPermissionFlag.SkipVerification |                                     SecurityPermissionFlag.UnmanagedCode); FileDialogPermission dlgPerm = new FileDialogPermission(                                     FileDialogPermissionAccess.Open); secPerm.Assert(); dlgPerm.Assert();     // Exception - can't have two Asserts in same method 

If you do need an assert that covers more than one permission, you will need to construct a permission set, and place the assert using the permission set. Details of how to do this are in MSDN - you'll need to look up the PermissionSet class. There's no problem, however, with having two modifiers of different types in the same method, for example an assert and a PermitOnly. Also, there's no problem with having more than one assert on the call stack provided that they were placed in different methods.

If you do need to cancel a stack modifier, you can do this using one of four static methods defined in CodeAccessPermission:RevertAssert(), RevertDeny(), RevertPermitOnly(), and RevertAll(). The reason that these methods are static is that, since only one modifier of each type can be present for the currently executing method, there's no need to supply any information about the modifier. RevertAssert(), for example, will cancel any assert modifier that is present for this method. As a result, the following code will run happily and not throw any exception:

 // secPerm and dlgPerm defined as above secPerm.Assert(); // Do something CodeAccessPermission.RevertAssert(); dlgPerm.Assert();    // No problem now 

Before we leave the subject of imperative security, there's one other point I ought to mention. So far, the discussion has focused entirely on those permissions that control access to resources. There are, however, some other permission classes that are also derived from CodeAccessPermission, and that represent rather the conditions used to establish membership of a code group. As an example, remember that one possible membership condition is that code should have originated from a certain web site. This is represented by the class SiteIdentityPermission. So if I want to check that all the assemblies on the call stack were downloaded from my own website, http://www.simonrobinson.com, I could do this:

 SiteIdentityPermission sitePerm = new SiteIdentityPermission(                                          "www.simonrobinson.com"); sitePerm.Demand(); 

It's quite instructive to type the above code into a project that you create on your local machine. You'll find the call to Demand() throws an exception because, obviously, code that is sitting on your machine cannot possibly satisfy the specified site identity.

In some ways I think it's misleading for Microsoft to refer to the identity permissions as permissions - since they are not really permissions to do anything, just statements of assembly identity. However, you can probably see the advantage of having the classes that implement these statements of identity being derived from CodeAccessPermission - they do provide a useful alternative way of checking or controlling which assemblies are allowed to execute code.

Declarative Security

So far the discussion in this chapter has focused entirely on imperative security, in which IL instructions in your code instantiate and call methods against the security permission classes in order to enforce security. The CLR also supports declarative security, in which the permissions required by an assembly, class, method, and so on, can be indicated by an attribute applied to that item. For example, suppose you have written a method that retrieves boot configurations, and you want to assert the FileIO permission so that this method can be used by code that doesn't have generic access to the file system. Using imperative security, the code would probably look something like this:

 string[] GetBootConfigurations() {    FileIOPermission filePerm = new FileIOPermission(FileIOPermissionAccess.Read,                                                     @"C:\Boot.ini");    filePerm.Assert();    // Get the data 

If you want to do the same thing using declarative security, the code will look more like this:

 [FileIOPermission(SecurityAction.Demand, Read = @"C:\Boot.ini")] string[] GetBootConfigurations() {    // Get the data 

Clearly, for declarative security to work we need the relevant attribute classes to have been defined. If you look in the System.Security.Permissions namespace in the documentation, you'll find that for every code access security permission class, Microsoft has defined a corresponding attribute, derived from System.Security.Permissions.CodeAccessSecurityAttribute (itself derived from System.Security.Permissions.SecurityAttribute). Thus there's a FileIOPermissionAttribute class, a SecurityPermissionAttribute class, and so on.

So what have you gained by using an attribute? Apart from less typing and simpler source code, the permissions this method is going to need are now indicated in the metadata, which means that other code can use reflection to check what permissions this method needs without actually invoking the method. There are also a few actions you can take with security permissions using declarative security that aren't available using imperative security. With imperative security you can demand, assert, deny, or permitonly a permission. With declarative security, there are further options, as shown in the table below. This is controlled through the first parameter that is passed to the FileIOPermissionAttribute in the above code. This parameter is an instance of the SecurityAction enumeration.

If you are applying a security attribute to a class or method, you can request the following actions using the SecurityAction enumeration:

Value

Also Available with Imperative Security

Description

Assert

Yes

Indicates that calling assemblies need not have this permission

Demand

Yes

Checks that all assemblies on the call stack have this permission

Deny

Yes

Future requests for this permission will be refused

InheritanceDemand

No

Requires any class that inherits from this class to have the given permission

LinkDemand

No

Requires the direct calling assembly to have the given permission (but not assemblies higher up the call stack)

PermitOnly

Yes

Future requests for any permission other than this permission will be refused

If an attribute is applied to a method, the relevant security check will be performed whenever that method is invoked. If it is applied to a class, the check will be made when the class is first used.

In addition, it is possible to apply security attributes to an assembly as a whole. This means that the check will be made when the assembly is loaded. In this case the options are:

  • RequestMinimum - the assembly will only load if these permissions are available.

  • RequestOptional - the assembly could use these permissions, but that's optional.

  • RequestRefuse - the assembly does not need these permissions; this is similar to a call to Deny() on these permissions.

Marking an assembly with attributes indicating the permissions that it will require can help systems administrators; for example, it means they will know how to adjust their security policy in order to allow your code to run. Using the RequestRefuse option can also help security by preventing your assembly from being abused by malicious code. This has a similar effect to denying a permission, but has the advantage that you only need to declare it once for the whole assembly.

Good Coding Practices

While we're on the subject of using CAS, it's worth saying a couple of words about good programming practices. As a developer, you have a responsibility to decide which security permissions your application really needs to perform its task, which of these should be demanded and which asserted, and also to ensure that you code your application in such a way that it does not end up 'accidentally' requiring more permissions than necessary. In order to assist in preventing malicious code from damaging your system, it can also be a good idea to request for permissions to be denied as far as possible if your assembly is to call into other assemblies that you don't trust (this includes callback methods).

When designing your application, you should think about which resources it uses. Don't unnecessarily do something that increases the number of permissions your application needs if there is an alternative. A typical issue here is where an application requires administrator privileges to execute due to careless choice of the files or registry keys it uses to store its internal data. It should go without saying that, unless you are writing some kind of administration package, the chances are that you want your application to be able to run from a normal user's account. The trouble is that a very large number of developers work using an administrator account - often the administrator account on their own machine. Let's face it, we developers install new software or otherwise play around with our machines so often that a lot of the time it's just not worth the hassle of not having administrator privileges. We need those permissions. The trouble is that many of the applications that we're coding up need to be able to run without admin permissions, and if we are working as administrators, we might easily not notice that some avoidable aspect of our design will fail if run from an account without administrator privileges. There is a useful article that discusses how these issues impact working with VS.NET at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/tchDevelopingSoftwareInVisualStudioNETWithNon-AdministrativePrivileges.asp.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net