Wrox Home  
Search
Professional C#, 3rd Edition
by Simon Robinson, Christian Nagel, Karli Watson, Jay Glynn, Morgan Skinner, Bill Evjen
June 2004, Paperback


Excerpt from Professional C#, 3rd Edition

Support for Security in the Framework

For .NET security to work, programmers must trust the CLR (Common Language Runtime) to enforce the security policy. When a call is made to a method that demands specific permissions (for example, accessing a file on the local drive), the CLR walks up the stack to ensure that every caller in the call chain has the permissions being demanded.

At this point the term performance is probably ringing in your mind, and clearly that is a concern, but to gain the benefits of a managed environment like .NET, it is the price we pay. The alternative is that assemblies which are not fully trusted could make calls to trusted assemblies and our system is open to attack.

For reference, the parts of the .NET Framework library namespace most applicable to this article are:

* System.Security.Permissions
* System.Security.Policy
* System.Security.Principal

Note that evidence-based code access security works in tandem with Windows logon security. If you attempt to run a .NET desktop application, the relevant .NET code access security permissions must be granted, but you as the logged-in user must also be using a Windows account that has the relevant permissions to execute the code. With desktop applications, this means the current user must have been granted the relevant rights to access the relevant assembly files on the drive. For Internet applications, the account under which Internet Information Server is running must have access to the assembly files.

Demanding Permissions

To see how demanding permissions work, create a Windows Forms application that just contains a button. When clicked, this performs an action that accesses the drive. If the application does not have the relevant permission to access the local drive (FileIOPermission), the button will be marked as unavailable (dimmed).

If you import the namespace System.Security.Permissions, you can change the constructor of the class Form1 to check for permissions by creating a FileIOPermission object, calling its Demand() method, and then acting on the result:

      public Form1()
      {
         InitializeComponent();

         try
         {
            FileIOPermission fileioperm = new
               FileIOPermission(FileIOPermissionAccess.AllAccess,@"c:\");
            fileioperm.Demand();
         }
         catch
         {
            button1.Enabled = false;
         }
      }

FileIOPermission is contained within the System.Security.Permissions namespace, which is the home to the full set of permissions, and also provides classes for declarative permission attributes and enumerations for the parameters that are used to create permissions objects (for example, when creating a FileIOPermission specifying whether we need full access, or read-only).

If you run the application from the local drive where the default security policy allows access to local storage, you will see a dialog box that resembles the one in Figure 1.

Professional C#, 3rd Edition - Figure 1
Figure 1

However, if you copy the executable to a network share and run it again, you are operating within the LocalIntranet permission sets, which blocks access to local storage, and the button will be dimmed as is shown in Figure 2.

Professional C#, 3rd Edition - Figure 2
Figure 2

If the functionality to make the button access the disk when clicking it is implemented, you will not have to write any security code, because the relevant class in the .NET Framework demands the file permissions, and the CLR ensures that each caller up the stack has those permissions before proceeding. If you run the application from the intranet, and it attempts to open a file on the local disk, you will see an exception unless the security policy has been altered to grant access to the local drive.

If you want to catch exceptions thrown by the CLR when code attempts to act contrary to its granted permissions, you can catch the exception of the type SecurityException, which provides access to a number of useful pieces of information, including a human-readable stack trace (SecurityException.StackTrace) and a reference to the method that threw the exception (SecurityException.TargetSite). SecurityException even provides you with the SecurityException.PermissionType property, which returns the type of Permission object that caused the security exception to occur. If you're having problems to diagnose security exceptions, this should be one of your first ports of call. Simply remove the try and catch blocks from the previous code to see the security exception.

Requesting Permissions

Demanding permissions is where you state clearly what you need at runtime; however, you can configure an assembly so it makes a softer request for permissions right at the start of execution where it states what it requires before it begins executing.

You can request permissions in three ways:

  • Minimum permissions specifies to the permissions your code must run.
  • Optional permissions specifies the permissions your code can use but is able to run effectively without.
  • Refused permissions specifies the permissions that you want to ensure are not granted to your code.

Why would you want to request permissions when your assembly starts? There are several reasons:

  • If your assembly needs certain permissions to run, it makes sense to state this at the start of execution rather than during execution to ensure the user does not experience a road block after beginning to work in your program.
  • You will only be granted the permissions you request and no more. Without explicitly requesting permissions your assembly might be granted more permissions than it needs to execute. This increases the risk of your assembly being used for malicious purposes by other code.
  • If you only request a minimum set of permissions, you are increasing the probability that your assembly will run, since you cannot predict the security policies that are in effect at an end user's location.

Requesting permissions is likely to be most useful if you're doing more complex deployment, and there is a higher risk that your application will be installed on a machine that does not grant the requisite permissions. It's usually preferable for the application to know right at the start of execution, if it will not be granted permissions, rather than halfway through execution.

To successfully request the permissions your assembly needs, you must keep track of exactly what permissions your assembly is using. In particular, you must be aware of the permission requirements of the calls your assembly is making into other class libraries, including the .NET Framework.

Let's look at three examples from an AssemblyInfo.cs file that demonstrate using attributes to request permissions. If you are following this with the code download, these examples can be found in the SecurityApp2 project. The first attribute requests that the assembly have UIPermission granted, which will allow the application access to the user interface. The request is for the minimum permissions, so if this permission is not granted, the assembly will fail to start:

using System.Security.Permissions;
[assembly:UIPermissionAttribute(SecurityAction.RequestMinimum, 
	Unrestricted=true)]

Next, there is a request that the assembly is refused access to the C:\ drive. This attribute's setting means the entire assembly will be blocked from accessing this drive:

 [assembly:FileIOPermissionAttribute(SecurityAction.RequestRefuse, 
	Read="C:\\")]

Finally, here's an attribute that requests our assembly be optionally granted the permission to access unmanaged code:

 [assembly:SecurityPermissionAttribute(SecurityAction.RequestOptional, 
                        Flags = SecurityPermissionFlag.UnmanagedCode)]

In this scenario you will want to add this attribute to an application that accesses unmanaged code in at least one place. In this case, it is specified that this permission is optional, which means that the application can run without the permission to access unmanaged code. If the assembly is not granted permission to access unmanaged code, and attempts to do so, a SecurityException will be raised, which the application should expect and handle accordingly.

The following table shows the full list of available SecurityAction enumeration values.

Security Action Description
Assert Allows code to access resources not available to the caller.
Demand Requires all callers in the call stack to have the specified permission.
Deny Denies a permission by forcing any subsequent demand for the permission to fail.
InheritanceDemand Requires derived classes to have the specified permission granted.
LinkDemand Requires the immediate caller to have the specified permission.
PermitOnly Similar to deny, subsequent demands for resources not explicitly listed by PermitOnly are refused.
RequestMinimum Applied at assembly scope; this contains a permission required for an assembly to operate correctly.
RequestOptional Applied at assembly scope; this asks for permissions the assembly can use, if available, to provide additional features and functionality.
RequestRefuse Applied at assembly scope when there is a permission you do not want your assembly to have.

When you consider the permission requirements of our application, you have to decide between one of two options:

  • Request all the permissions you need at the start of execution, and degrade gracefully or exit if those permissions are not granted.
  • Avoid requesting permissions at the start of execution, but be prepared to handle security exceptions throughout our application.

After an assembly has been configured using permission attributes in this way, you can use the permview.exe utility to view the permissions by aiming it at the assembly file that contains the assembly manifest:

>permview.exe <path>\SecurityApp2\bin\Debug\SecurityApp2.exe

The output for an application using the three previously discussed attributes looks like this:

Microsoft (R) .NET Framework Permission Request Viewer.  Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

minimal permission set:
<PermissionSet class="System.Security.PermissionSet"
                      version="1">
   <IPermission class="System.Security.Permissions.UIPermission, 
mscorlib,
                       Version=1.0.5000.0, Culture=neutral, 
                       PublicKeyToken=b77a5c561934e089"
                       version="1"
                       Unrestricted="true"/>
   </PermissionSet>

optional permission set:
<PermissionSet class="System.Security.PermissionSet" version="1">
   <IPermission class="System.Security.Permissions.SecurityPermission, 
mscorlib,
                       Version=1.0.5000.0, Culture=neutral, 
                       PublicKeyToken=b77a5c561934e089"
                       version="1"
                       Flags="UnmanagedCode"/>
</PermissionSet>

refused permission set:
<PermissionSet class="System.Security.PermissionSet"
                      version="1">
   <IPermission class="System.Security.Permissions.FileIOPermission, 
mscorlib,
                       Version=1.0.5500.0, Culture=neutral, 
                       PublicKeyToken=b77a5c561934e089"
                       version="1"
                       Read="c:\"/>
</PermissionSet>

In addition to requesting permissions, you can also request permissions sets; the advantage is that you can request a whole set of permissions all at once. Since the Everything permission set can be altered through the security policy while an assembly is running, it cannot be requested. For example, if an assembly requests at runtime that it must be granted all permissions in the Everything permission set to execute, and the administrator then tightens the Everything permission set while the application is running, he might be unaware that the permission set is still operating with a wider set of permissions than the policy dictates.

Here's an example, of how to request a built-in permission set:

 [assembly:PermissionSetAttribute(SecurityAction.RequestMinimum, 
                                 Name = "FullTrust")]

In this example the assembly requests that as a minimum it needs the FullTrust built-in permission set granted. If this set of permissions is not granted, the assembly will throw a security exception at runtime.

Implicit Permission

When permissions are granted, there is often an implicit statement that we are also granted other permissions. For example, if you assign the FileIOPermission for C:\ there is an implicit assumption that there is also access to its subdirectories (Windows account security allowing).

If you want to check whether a granted permission implicitly brings another permission as a subset, you can do this:

// Example from SecurityApp3

   class SecurityApp3
   {
      static void Main(string[] args)
      {
         CodeAccessPermission permissionA = 
            new FileIOPermission(FileIOPermissionAccess.AllAccess, @"C:\");
         CodeAccessPermission permissionB = 
            new FileIOPermission(FileIOPermissionAccess.Read, @"C:\temp");
         if (permissionB.IsSubsetOf(permissionA))
         {
            Console.WriteLine("PermissionB is a subset of PermissionA");
         }
         else
         {
            Console.WriteLine("PermissionB is NOT a subset of PermissionA");
         }
      }
   }
  

The output looks like this:

PermissionB is a subset of PermissionA

Denying Permissions

Under certain circumstances you might want to perform an action and be absolutely sure that the method that is called is acting within a protected environment where it cannot do anything untoward. For example, let's say you want to make a call to a third-party class in a way that it will not access the local disk.

To do that, create an instance of the permission you want to ensure the method is not granted, and then call its Deny() method before making the call to the class:

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
namespace Wrox.ProCSharp.Security
{
   class SecurityApp4
   {
      static void Main(string[] args)
      {
         CodeAccessPermission permission = 
            new FileIOPermission(FileIOPermissionAccess.AllAccess,@"C:\");
         permission.Deny();
         UntrustworthyClass.Method();
         CodeAccessPermission.RevertDeny();
      }
   }
   class UntrustworthyClass
   {
      public static void Method()
      {
         try
         {
            StreamReader din = File.OpenText(@"C:\textfile.txt"); 
         }
         catch
         {
            Console.WriteLine("Failed to open file");
         }
      }
   }
}
  

If you build this code the output will state Failed to open file, as the untrustworthy class does not have access to the local disk.

Note that the Deny() call is made on an instance of the permission object, whereas the RevertDeny() call is made statically. The reason for this is that the RevertDeny() call reverts all deny requests within the current stack frame; this means if you have made several calls to Deny() you only need to make one follow-up call to RevertDeny().

Asserting Permissions

Imagine that there is an assembly that has been installed with full trust on a user's system. Within that assembly there is a method that saves auditing information to a text file on the local disk. If later an application is installed that wants to make use of the auditing feature, it will be necessary for the application to have the relevant FileIOPermission permissions to save the data to disk.

This seems excessive, however, because all we really want to do is perform a highly restricted action on the local disk. In these situations, it would be useful if assemblies with limiting permissions could make calls to more trusted assemblies that can temporarily increase the scope of the permissions on the stack, and perform operations on behalf of the caller that it does not have the permissions to do itself.

To achieve this, assemblies with high enough levels of trust can assert permissions that they require. If the assembly has the permissions it needs to assert additional permissions, it removes the need for callers up the stack to have such wide-ranging permissions.

The code that follows contains a class called AuditClass that implements a method called Save(), which takes a string and saves audit data to C:\audit.txt. The AuditClass method asserts the permissions it needs to add the audit lines to the file. To test it out, the Main() method for the application explicitly denies the file permission that the Audit method needs:

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
namespace Wrox.ProCSharp.Security
{
   class SecurityApp5
   {
      static void Main(string[] args)
      {
         CodeAccessPermission permission = 
            new FileIOPermission(FileIOPermissionAccess.Append, 
                                 @"C:\audit.txt");
         permission.Deny();
         AuditClass.Save("some data to audit");
         CodeAccessPermission.RevertDeny();
      }
   }
   class AuditClass
   {
      public static void Save(string value)
      {
         try
         {
            FileIOPermission permission = 
               new FileIOPermission(FileIOPermissionAccess.Append,
                                    @"C:\audit.txt");
            permission.Assert();
            FileStream stream = new FileStream(@"C:\audit.txt", 
               FileMode.Append, FileAccess.Write);

            // code to write to audit file here...
            CodeAccessPermission.RevertAssert();
            Console.WriteLine("Data written to audit file");
         }
         catch
         {
            Console.WriteLine("Failed to write data to audit file");
         }
      }
   }
}
  

When this code is executed, you'll find that the call to the AuditClass method does not cause a security exception, even though when it was called it did not have the required permissions to carry out the disk access.

Like RevertDeny(), RevertAssert() is a static method, and it reverts all assertions within the current frame.

It's important to be very careful when using assertions. We are explicitly assigning permissions to a method that has been called by code that might not have those permissions, and this could open a security hole. For example, in the auditing example, even if the security policy dictated that an installed application cannot write to the local disk, our assembly would be able to write to the disk when the auditing assembly asserts FileIOPermissions for writing. To perform the assertion the auditing assembly must have been installed with permission for FileIOAccess and SecurityPermission. The SecurityPermission allows an assembly to perform an assert, and the assembly will need both the SecurityPermission and the permission being asserted to complete successfully.

Creating Code Access Permissions

The .NET Framework implements code access security permissions that provide protection for the resources that it exposes. However, there might be occasions when you want to create your own permissions. You can do so by subclassing CodeAccessPermission. Deriving a custom permission class from the class CodeAccessPermission gives you the benefits of the .NET code access security system, including stack walking and policy management.

Here are two examples of cases where you might want to roll your own code access permissions:

  • Protecting a resource not already protected by the Framework. For example, you have developed a .NET application for home automation that is implemented by using an onboard hardware device. Creating your own code access permissions, you have a highly granular level of control over the access given to the home automation hardware.
  • Providing a finer degree of management than existing permissions. For example, although the .NET Framework provides permissions that allow granular control over access to the local file system, you might have an application where you want to control access to a specific file or folder much more tightly. In this scenario, you might find it useful to create a code access permission that relates specifically to that file or folder; without that permission no managed code can access that area of the disk.

Declarative Security

You can deny, demand, and assert permissions by calling classes in the .NET Framework. However, you can also use attributes and specify permission requirements declaratively.

The main benefit of using declarative security is that the settings are accessible through reflection. This can be of enormous benefit to system administrators, who often will want to view the security requirements of applications.

For example, we can specify that a method must have permission to read from C:\ to execute:

using System;
using System.Security.Permissions;
namespace Wrox.ProCSharp.Security
{
   class SecurityApp6
   {
      static void Main(string[] args)
      {
         MyClass.Method();
      }
   }

   [FileIOPermission(SecurityAction.Assert, Read="C:\\")]
   class MyClass
   {
      public static void Method()
      {   

         // implementation goes here

      }
   }
}
  

Be aware that if you use attributes to assert or demand permissions, you cannot catch any exceptions that are raised if the action fails, because there is no imperative code around in which you can place a try-catch-finally clause.