Support for Security in the Framework


For .NET security to work, programmers must trust the CLR 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 that is the price you pay. The alternative is that assemblies that are not fully trusted could make calls to trusted assemblies and your system would be open to being attacked.

For reference, the parts of the .NET Framework library namespace most applicable to this chapter 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 use a Windows account that has the relevant permissions to execute the code. With desktop applications, 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 be granted access to the assembly files.

Demanding Permissions

To see how demanding permissions work, create a Windows Forms application that just contains a button. When the button is clicked, a file on the local file system is accessed. 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 fileIOPermission = new          FileIOPermission(FileIOPermissionAccess.AllAccess,@"c:\");       fileIOPermission.Demand();    }    catch (SecurityException)    {       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 creating a FileIOPermission specifying whether read-only or full access is needed).

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 19-5.

image from book
Figure 19-5

However, if you copy the executable to a network share and run it again, you are operating within the LocalIntranet permission set, which blocks access to local storage, and the button will be disabled as shown in Figure 19-6.

image from book
Figure 19-6

Within the implementation of the click event handler, there’s no need to check the required security because the relevant class in the .NET Framework already demands the file permission, 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 have problems with security exceptions, this should be one of your first parts to diagnose. Simply remove the try and catch blocks from the previous code to see the security exception.

Requesting Permissions

As discussed in the previous section, 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. The assembly can specify the required permissions before it begins executing.

You can request permissions in three ways:

  • Minimum permissions specify the permissions your code must run.

  • Optional permissions specify the permissions your code can use but is able to run effectively without.

  • Refused permissions specify 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 that 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 nothing 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 request only a minimum set of permissions, you are increasing the probability that your assembly will run, because you cannot predict the security policies that are effective at the 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 required permissions. It’s usually preferable for the application to know right at the start if it will not be granted permissions, rather than halfway through execution.

In Visual Studio 2005, you can check the required permissions of an application by selecting the Security tab with the properties (see Figure 19-7). Clicking the Calculate Permissions button checks the code of the assembly and lists all required permissions.

image from book
Figure 19-7

Instead of using Visual Studio, you can use the command-line tool permcalc.exe to calculate the required permissions of an assembly. This tool is new with .NET 2.0.

The command line:

 permcalc.exe –show –stacks -cleancache DemandingPermissions.exe 

creates an XML file that contains all required permissions. With the option –show the XML file is opened immediately. The option –stacks adds the stack information to the XML file, for you to see where the permissions demand originated from.

The required permissions can be added as attributes to the assembly. Following are three examples that demonstrate using attributes to request permissions. If you are following this with the code download, you can find these examples in the RequestingPermissions 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 be refused access to the C:\ drive. This attribute’s setting means that the entire assembly will be blocked from accessing this drive:

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

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

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

In this scenario, you 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; some of these values are covered in more detail later on.

Open table as spreadsheet

SecurityAction Enumeration

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

DemandChoice

Requires all callers in the stack to have one of the specified permissions

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

LinkDemandChoice

Requires the immediate caller to have one of the specified permissions

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 your application, you have to decide between 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 your application.

After an assembly has been configured using permission attributes in this way, you can use the permcalc.exe utility to show the required permissions by aiming at the assembly file that contains the assembly manifest using the –assembly option of the permcalc.exe utility:

 >permcalc.exe –show –assembly RequestingPermissions.exe 

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

 Microsoft (R) .NET Framework Permissions Calculator. Copyright (C) Microsoft Corporation 2005. All rights reserved. Analyzing... |-----------------------------------------------------------------------------| ............................................................................... RequestingPermissions.exe Minimal permission set: <PermissionSet  version="1"> <IPermission  version="1" Unrestricted="true"/> </PermissionSet> Optional permission set: <PermissionSet  version="1"> <IPermission  version="1" Flags="SecurityPermissionFlag.UnmanagedCode" /> </PermissionSet> Refused permission set: <PermissionSet  version="1"> <IPermission  version="1" Read="C:"/> </PermissionSet> Generating output... Writing file: RequestingPermissions.exe.PermCalc.xml...

In addition to requesting permissions, you can also request a complete permissions set; the advantage is that you don’t have to deal with every single permission. However, you can only request permission sets that cannot be altered. The Everything permission set can be altered through the security policy while an assembly is running, so it cannot be requested.

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 you 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.

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

  // Example ImplicitPermissions    class Program    {       static void Main()       {          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");          }       }    } 

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. An assembly shouldn’t be allowed to do anything unexpected. For example, say that you want to make a call to a third-party class in a way that it will not access the local disk.

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

  // Example DenyingPermissions using System; using System.IO; using System.Security; using System.Security.Permissions; namespace Wrox.ProCSharp.Security {    class Program    {       static void Main()       {          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, because 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; if you have made several calls to Deny(), you need only make one follow-up call to RevertDeny().

Asserting Permissions

Imagine that an assembly 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 you 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. The caller doesn’t need to have the permissions itself.

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. For testing it, the Main() method for the application explicitly denies the file permission that the Audit method needs:

  // Example AssertingPermissions using System; using System.IO; using System.Security; using System.Security.Permissions; namespace Wrox.ProCSharp.Security {    class Program    {       static void Main()       {          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 is important to be very careful when using assertions. You 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, your assembly would be able to write to the disk when the auditing assembly asserts FileIOPermissions for writing.

However, 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.

An example of when this is used with the .NET Framework is for classes that require unmanaged code access as they invoke Win32 API calls or COM components. The caller of these classes doesn’t require unmanaged code access. The permissions that are required by the callers are verified by these classes.

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, which you can do by subclassing CodeAccessPermission. Deriving a custom permission class from the class CodeAccessPermission gives 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, suppose that you have developed a .NET application for home automation that is implemented by using an onboard hardware device. Creating your own code access permissions gives you 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, you can specify that a method must have permission to read from C:\ to execute:

  // Example DeclarativeSecurity using System; using System.Security.Permissions; namespace Wrox.ProCSharp.Security {    class Program    {       static void Main()       {          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.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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