CAS Under the Hood

We're now going to have a look in more detail at how the implementation of code access security works under the hood. Although we discuss code access security here, many of the principles involved, particularly the way that the underlying security engine exposes numerous hooks that managed classes can plug into to customize security policy, apply equally well to role-based security.

One thing that might surprise you is the extent to which the CLR is divorced from the implementation of security. To see this, let's look once again at that FileIO permission. Of course, we've all been brought up to believe that the FileIO permission is the way that the CLR protects against unauthorized use of files. It's very tempting to conclude from this that there must be some mechanism in the CLR that detects if an application is trying to access a file, and therefore checks if it has the appropriate permission. If that's how you've imagined the situation, think again. That's how Windows/NT security works, not how CLR security works. In the CLR, checking that code has sufficient permissions to carry out some operation is (apart from the exceptions noted earlier) the responsibility of the IL code itself, not the responsibility of the CLR. So when you use the System.IO classes to access files and folders, it is those classes that internally are implemented to make sure that the appropriate security is obtained. Returning to the code snippet I presented early on in the chapter, I indicated that when accessing the file C:\Boot.ini, something similar to the following would be executed:

 FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.Read,                                              @"C:\Boot.ini"); perm.Demand(); 

The crucial point to understand is that the CLR has no idea that FileIOPermission has anything to do with file access. All the CLR knows is that some code has called CodeAccessPermission.Demand() for this particular permission. It is this method that walks up the stack checking if each method in turn has this permission (as you might guess, this process is performed in an IL internalcall method). All the CLR knows is that it must check against the evidence for each assembly to see what code groups that assembly is in, and therefore what permission set is available.

I mentioned earlier that this looks at first sight like this exposes a security loophole: if the demanding of permissions is done by the assembly and not automatically imposed by the CLR, what is there to stop someone from writing an assembly that does some dangerous operation, but without asking for the relevant permissions? For example, an assembly that manipulates the file system, but without demanding the FileIO permission first? It is perfectly possible for someone to write such an assembly. However, in general this shouldn't cause problems, and there is in fact no security loophole for the following reasons:

  • In practice, under the hood, all the potentially dangerous operations can only be accessed by calling some unmanaged code at some point. This comes back to the point I emphasized in Chapter 3 that no IL instruction can do any more than access the local memory that is available to the application domain. There is no IL instruction, for example, that accesses a file or the registry. You can only perform those operations by calling into unmanaged code. This means that the assembly in question will need to have permission to call unmanaged code - and that's only going to happen if it's a very highly trusted assembly. For example, if your assembly wants to manipulate arbitrary files, then it will either have to call the API functions directly (needs unmanaged code permission) or use the System.IO classes (needs FileIO permission and internally calls into unmanaged code anyway).

  • On Windows NT/2K/XP, the CLR's security sits on top of Windows security. Since Windows security is controlled by the OS and has nothing to do with the CLR, you still can't do anything in managed code that your account wouldn't have rights to do in unmanaged code (though obviously this doesn't protect against malicious code running under a highly trusted account).

Indeed, the fact that trusted code can arrange for CLR permissions to be circumvented in a controlled manner is a bonus: this ability provides the means by which less trusted code can be allowed to access resources in a very controlled and safe manner. And, as we saw earlier, there is a perfect example of this sitting in the framework class library: the isolated storage classes. The isolated storage classes also access the file system. However, they do not request FileIO permission - instead they request IsolatedStorageFile permission, a permission that can be considered less restrictive in the sense that the default security policy allows more code this permission. Why do we accept the existence of these classes? Because the isolated storage classes have been carefully written so that they only access carefully defined areas of the file system. It is simply not possible for client code to call corrupt or damage system files by calling methods on the isolated storage classes in the same way that it could with the System.IO classes.

Having said all that, I should point out that it would theoretically be possible to open a security loophole by writing some code that asserts permissions and then allows callers to abuse this fact. For example, if you wrote an assembly whose methods allowed unrestricted access to the file system, but where that assembly invoked the System.IO classes and asserted the relevant FileIO permission, and you placed this assembly on a local drive so that it was fully trusted, then partially trusted code would be able to use such an assembly to wreak havoc on your system. But here .NET is no different from classic security. It's very hard to guard against some trusted individual opening up a security loophole by writing bad code. Hopefully, once you understand how CAS works, you'll be less likely accidentally to do that through poor use of Assert().

I should also point out one weakness in the whole infrastructure. Very often when you install software, you do so by running some .msi or similar file from an administrator account. If you do this, then there is in principle nothing to stop that file from modifying your CLR security policy, which, if not done carefully, might open a security loophole. I'm already aware of one well-known company some of whose software comes with an MSI file that adjusts security policy to give full trust to all code signed with that company's certificate - and does not warn the user that this change is being made. While I can understand why a large company that is selling sophisticated managed software might want to make that change in order to make life easier for its own programmers, I can't emphasize enough that good coding practice requires that you only demand those permissions that your code really needs.

The CAS Security Classes

If security policy is implemented by managed classes, there clearly must be some mechanism by which the CLR can find out which classes it needs to instantiate in order to implement its security policy. This information is indicated in the .config XML files that control security. We don't have space here to go into the process in detail, but to get a flavor of it, consider the LocalIntranet built-in permission set, and in particular its Security permission. We saw earlier that this permission set gives code the Security permission to execute code, and to assert permissions. If we look in the CLR's machine.config file, we find this XML tag, which introduces the LocalIntranet permission set:

 <PermissionSet     version="1" Name="LocalIntranet"    Description="Default rights given to applications on the local intranet"> 

The class attribute of this tag indicates that the class that must be instantiated in order to implement this permission set is a class called NamedPermissionSet. This is located in the System.Security namespace. There is no indication in the tag about where to search for this class. In fact, the CLR's security subsystem will search in those assemblies that have been registered as allowed to implement security policy - we'll see how to do this soon when we look at the LightDetector sample.

The <PermissionSet> element contains a number of child elements, one for each permission defined in this set. Among these elements is the following:

 <IPermission               version="1"              Flags="Assertion, Execution" /> 

This <IPermission> element represents a permission within the permission set, and once again the class attribute indicates which class should be instantiated in order to represent this element. This class must implement the System.Security.IPermission interface, which defines the Demand() method, as well as methods to handle combining permissions. This permission class should also normally implement two other System. Security interfaces, IStackWalk and ISecurityEncodable.IStackWalk also defines the Demand() method, as well as the Assert() method and a couple of other methods that control the process of walking up the stack, checking which assemblies have which permissions. ISecurityEncodable defines two methods, FromXml() and ToXml(), which are respectively able to initialize a class instance by reading from an XML file and writing out the XML element from the state of the object. All these interfaces are implemented by CodeAccessPermission, so it's usual for security classes to derive from this class in order to pick up much of the implementation of these interfaces for free.

Beyond the class and version attributes, the remaining attributes in the <IPermission> element are variable, and will depend on the permission class instantiated. The idea is that the CLR will instantiate the named class, and then initialize it by invoking that object's ISecurityEncodable.FromXml() method, handing it the entire XML element from the security configuration file - so that class must be able to interpret this XML stream. With the above XML element, we'll end up with a SecurityPermission object that has been initialized to indicate that it should allow requests only for the Assertion and Execution subpermissions.

We are now in a position to understand better what happens when some code demands a permission. When each assembly is loaded into the process, the CLR will read the XML representation of the security policy defined in the various .config files to instantiate permission classes that define the permissions available to that assembly. For the most part, nothing will be done with these objects for the time being, although the CLR will check the SecurityPermission object to make sure that the assembly does have permission to execute! However, later on, if any code calls methods to demand permissions, this set of permission classes is likely to be checked, most commonly as a result of a call to CodeAccessPermission.Demand().

Declarative Security Under the Hood

As mentioned earlier, the security attributes are all derived from the System.Security.Permissions.SecurityAttribute attribute. Knowledge of this attribute has been hard-coded into the CLR, so it knows to take some special action if it encounters this attribute. In particular, after instantiating the SecurityAttribute-derived class, it will call the SecurityAttribute.CreatePermission() method. All classes that derive from SecurityAttribute should implement this method to create an instance of the corresponding security permission class, initialized to the correct state as far as subpermissions are concerned. Once the CLR has this object, it can manipulate it in the same manner as for imperative security.

In order to enable declarative security, every security permission class should have a corresponding attribute class. If any permission class does not have a corresponding attribute class defined, it won't be possible to use declarative security for that class, although you can still use the class for imperative security.

This is really as far as we are going to go with exploring the principles of .NET security. We are now going to present a couple of samples that will demonstrate how these principles are put into practice.



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