The Security Stack Walk

for RuBoard

The type of stack walk that concerns us here is, of course, the security stack walk. Why would the security system need to walk the stack at all?

Let's take a simple example. Given the following code snippet from assembly MyDatabase ,

 public class MyDatabase {     public void BackupDatabase(String filename)     {         FileStream stream = new FileStream(filename, FileMode.Create);         // Code to save database to newly created file...     } } 

when the BackupDatabase method calls the constructor for FileStream , a security demand will be initiated to ensure that the caller has sufficient privilege to create the named file. Recall that the .NET Framework security system is primarily based on code access security. In other words, the security demand is based around the identity of the code making the call rather than the user account executing the thread. The operating system will still make the user identity checks prior to the actual file system operation, but first the runtime security system will have a chance to disallow the action based on the calling assembly.

So, assuming that within the implementation of the FileStream constructor a security demand is initiated for FileIOPermission , how does the runtime locate the calling assembly? The user identity is easy to find; it's stored in per-thread context (a collection of data kept for each thread in the process). The runtime could store the current assembly in the per-thread context as well, but this presents some problems. First, the current assembly at the time the security demand is initiated is, in fact, the assembly containing the implementation of FileStream , not MyDatabase . So the runtime would have to record not just the current assembly, but also the prior assembly as well. It soon becomes obvious that the entire list of assemblies invoked would need to be stored in the per-thread context. Second, the runtime would need to spot assembly transitions (calls from a method in one assembly into a method in another assembly) to track this information (including the return from such a call). Because method calls crossing an assembly boundary are reasonably common (at least with respect to security demands), this approach would entail undesirable performance penalties.

Instead, the security system utilizes the fact that we've already built a list of assemblies involved in the current operation ”the stack itself. Determining the calling assembly is as simple as walking the stack. The frame for the FileStream constructor is simply ignored because it initiated the demand, but then we find the frame for MyDatabase.BackupDatabase . After the security system knows the method called, there is a simple mapping back to the enclosing class and then to the assembly. The assembly, being the unit of code to which the .NET Framework security system grants permissions, can then be queried to determine whether sufficient trust has been given to allow progress.

But the security system doesn't stop there. It continues on down the stack, testing each assembly it finds, until it runs out of stack frames or reaches an assembly with insufficient permissions. What's the purpose behind this behavior?

To answer this, let's look more closely at the implementation of MyDatabase.BackupDatabase . Notice that the method is public (and exported from a public class) and that it takes a filename that is blindly used to create the database backup file. In fact, this method represents a classic security hole, the so-called luring attack. Any untrusted assembly (say, one downloaded from the Internet with no additional credentials) can use this method to write to an arbitrary file on the user's disk. They might be limited in their ability to control the contents of that file and the operations possible will be constrained by the privileges given by the operating system to the user executing the thread, but the destructive potential is still very high.

This is actually a common security error, and it's frequently made by programmers who are not even aware that security demands are taking place or that security is an issue they need to worry about. Frequently, the mistakes can be more subtle. The author of BackupDatabase may prepend a directory to the filename passed in, in an effort to constrain where the backup file is written, forgetting that the caller may pass in a filename of the form ..\BadFile.dat .

So let's assume the worst: A malicious assembly, BadAssembly , has a method BadClass.TrashFile , which makes a call to MyDatabase.BackupDatabase passing a filename of c:\ windows \system.ini . How will the security system cope with this?

As before, the frame for the FileStream constructor will be skipped , and then the permissions for the MyDatabase assembly will be checked and found to be sufficient. Then the stack walk will move on to the frame for the TrashFile method. When the permissions for the corresponding assembly BadAssembly are checked and found to be insufficient, the stack walk is aborted and a security exception thrown (see the diagram in Figure 7.4).

Figure 7.4. Catching a malicious caller using a full stack walk.

graphics/07fig04.gif

The .NET Framework security system didn't intrinsically know that BadAssembly was malicious, just that the system administrator had not placed enough trust in that assembly to allow it to perform potentially damaging actions. Equally, the security system didn't know for sure whether BackupDatabase was badly written and represented a security hole. In the absence of any other data, it made a paranoid decision that any flow of control from the untrusted to the trusted assembly represented a security risk. In the world of security, the paranoid decision is usually a good default.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

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