Visual CSharp 2005 Recipes (2006) [eng]
.pdfC H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y |
399 |
How It Works
As the runtime loads each assembly, it ensures that the assembly’s grant set (the permissions assigned to the assembly based on the security policy) includes the Execution element of SecurityPermission. The runtime implements a lazy policy resolution process, meaning that the grant set of an assembly is not calculated until the first time a security demand is made against the assembly. Not only does execution permission checking force the runtime to check that every assembly has the execution permission, but it also indirectly causes policy resolution for every assembly loaded, effectively negating the benefits of lazy policy resolution. These factors can introduce a noticeable delay as assemblies are loaded, especially when the runtime loads a number of assemblies together, as it does at application startup.
In many situations, simply allowing code to load and run is not a significant risk, as long as all other important operations and resources are correctly secured using CAS and operating system security. The SecurityManager class contains a set of static methods that provide access to critical security functionality and data. This includes the CheckExecutionRights property, which turns on and off execution permission checks.
To modify the value of CheckExecutionRights, your code must have the ControlPolicy element of SecurityPermission. The change will affect the current process immediately, allowing you to load assemblies at runtime without the runtime checking them for execution permission. However, the change will not affect other existing processes. You must call the SavePolicy method to persist the change to the Windows registry for it to affect new processes.
The Code
The following example contains two methods (ExecutionCheckOn and ExecutionCheckOff) that demonstrate the code required to turn execution permission checks on and off and persist the configuration change.
using System.Security;
namespace Apress.VisualCSharpRecipes.Chapter11
{
class Recipe11_03
{
//A method to turn on execution permission checking
//and persist the change.
public void ExecutionCheckOn()
{
//Turn on execution permission checks. SecurityManager.CheckExecutionRights = true;
//Persist the configuration change. SecurityManager.SavePolicy();
}
//A method to turn off execution permission checking
//and persist the change.
public void ExecutionCheckOff()
{
//Turn off execution permission checks. SecurityManager.CheckExecutionRights = false;
//Persist the configuration change. SecurityManager.SavePolicy();
}
}
}
400 C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y
Notes
The .NET runtime allows you to turn off the automatic checks for execution permissions from within code or by using Caspol.exe. When you enter the command caspol –e off or its counterpart caspol –e on from the command line, the Caspol.exe utility actually sets the CheckExecutionRights property of the SecurityManager class before calling SecurityManager.SavePolicy.
11-4. Ensure the Runtime Grants Specific Permissions to Your Assembly
Problem
You need to ensure that the runtime grants your assembly those code access permissions that are critical to the successful operation of your application.
Solution
In your assembly, use permission requests to specify the code access permissions that your assembly must have. You declare permission requests using assembly-level code access permission attributes.
How It Works
The name permission request is a little misleading given that the runtime will never grant permissions to an assembly unless security policy dictates that the assembly should have those permissions. However, naming aside, permission requests serve an essential purpose, and although the way the runtime handles permission requests might initially seem strange, the nature of CAS does not allow for any obvious alternative.
Permission requests identify permissions that your code must have to function. For example, if you wrote a movie player that your customers could use to download and view movies from your web server, it would be disastrous if the user’s security policy did not allow your player to open a network connection to your media server. Your player would load and run, but as soon as the user tried to connect to your server to play a movie, the application would crash with the exception System.Security.SecurityException. The solution is to include in your assembly a permission request for the code access permission required to open a network connection to your server (System.Net.WebPermission or System.Net.SocketPermission, depending on the type of connection you need to open).
The runtime honors permission requests using the premise that it’s better that your code never load than to load and fail sometime later when it tries to perform an action that it does not have permission to perform. Therefore, if after security policy resolution the runtime determines that the grant set of your assembly does not satisfy the assembly’s permission requests, the runtime will fail to load the assembly and will instead throw the exception System.Security.Policy.PolicyException. Since your own code failed to load, the runtime will handle this security exception during the assembly loading and transform it into a System.IO.FileLoadException exception that will terminate your program.
When you try to load an assembly from within code (either automatically or manually), and the loaded assembly contains permission requests that the security policy does not satisfy, the method you use to load the assembly will throw a PolicyException exception, which you must handle appropriately.
C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y |
401 |
To declare a permission request, you must use the attribute counterpart of the code access permission that you need to request. All code access permissions have an attribute counterpart that you use to construct declarative security statements, including permission requests. For example, the attribute counterpart of SocketPermission is SocketPermissionAttribute, and the attribute counterpart of WebPermission is WebPermissionAttribute. All permissions and their attribute counterparts follow the same naming convention and are members of the same namespace.
When making a permission request, it’s important to remember the following:
•You must declare the permission request after any top-level using statements but before any namespace or type declarations.
•The attribute must target the assembly, so you must prefix the attribute name with assembly.
•You do not need to include the Attribute portion of an attribute’s name, although you can.
•You must specify SecurityAction.RequestMinimum as the first positional argument of the attribute. This value identifies the statement as a permission request.
•You must configure the attribute to represent the code access permission you want to request using the attribute’s properties. Refer to the .NET Framework SDK documentation for details of the properties implemented by each code access security attribute.
•The permission request statements do not end with a semicolon (;).
•To make more than one permission request, simply include multiple permission request statements.
The Code
The following example is a console application that includes two permission requests: one for SocketPermission and the other for SecurityPermission. If you try to execute the PermissionRequestExample application and your security policy does not grant the assembly the requested permissions, you will get a PolicyException exception, and the application will not execute. Using the default security policy, this will happen if you run the assembly from a network share, because assemblies loaded from the intranet zone are not granted SocketPermission.
using System; using System.Net;
using System.Security.Permissions;
//Permission request for a SocketPermission that allows the code to open
//a TCP connection to the specified host and port. [assembly:SocketPermission(SecurityAction.RequestMinimum,
Access = "Connect", Host = "www.fabrikam.com", Port = "3538", Transport = "Tcp")]
//Permission request for the UnmanagedCode element of SecurityPermission,
//which controls the code's ability to execute unmanaged code. [assembly:SecurityPermission(SecurityAction.RequestMinimum,
UnmanagedCode = true)]
namespace Apress.VisualCSharpRecipes.Chapter11
{
class Recipe11_04
{
public static void Main()
{
// Do something. . .
402 C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y
// Wait to continue.
Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();
}
}
}
11-5. Limit the Permissions Granted to Your Assembly
Problem
You need to restrict the code access permissions granted to your assembly, ensuring that people and other software can never use your code as a mechanism through which to perform undesirable or malicious actions.
Solution
Use declarative security statements to specify optional permission requests and permission refusal requests in your assembly. Optional permission requests define the maximum set of permissions that the runtime will grant to your assembly. Permission refusal requests specify particular permissions that the runtime should not grant to your assembly.
How It Works
In the interest of security, it’s ideal if your code has only those code access permissions required to perform its function. This minimizes the opportunities for people and other code to use your code to carry out malicious or undesirable actions. The problem is that the runtime resolves an assembly’s permissions using security policy, which a user or an administrator configures. Security policy could be different in every location where your application is run, and you have no control over what permissions the security policy assigns to your code.
Although you cannot control security policy in all locations where your code runs, the .NET Framework provides two mechanisms through which you can reject permissions granted to your assembly:
•Refuse request: This allows you to identify specific permissions that you do not want the runtime to grant to your assembly. After policy resolution, if the final grant set of an assembly contains any permission specified in a refuse request, the runtime removes that permission.
•Optional permission request: This defines the maximum set of permissions that the runtime can grant to your assembly. If the final grant set of an assembly contains any permissions other than those specified in the optional permission request, the runtime removes those permissions. Unlike as with a minimum permission request (discussed in recipe 11-4), the runtime will not refuse to load your assembly if it cannot grant all of the permissions specified in the optional request.
You can think of a refuse request and an optional request as alternative ways to achieve the same result. The approach you use depends on how many permissions you want to reject. If you want to reject only a handful of permissions, a refuse request is easier to code. However, if you want to reject a large number of permissions, it’s easier to code an optional request for the few permissions you want, which will automatically reject the rest.
404 C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y
Solution
Use the Permissions Calculator (Permcalc.exe) supplied with the .NET Framework SDK version 2.0 or the Permissions View tool (Permview.exe) supplied with the .NET Framework SDK versions 1.0 and 1.1.
How It Works
To configure security policy correctly, you need to know the code access permission requirements of the assemblies you intend to run. This is true of both executable assemblies and libraries that you access from your own applications. With libraries, it’s also important to know which permissions the assembly refuses so that you do not try to use the library to perform a restricted action, which would result in a System.Security.SecurityException exception.
The Permissions View tool (Permview.exe) supplied with the .NET Framework SDK versions 1.0 and 1.1 allows you to view the minimal, optional, and refuse permission requests made by an assembly. By specifying the /decl switch, you can view all of the declarative security statements contained in an assembly, including declarative demands and asserts. This can give you a good insight into what the assembly is trying to do and allow you to configure security policy appropriately. However, Permview.exe does not show the imperative security operations contained within the assembly.
The Permissions Calculator (Permcalc.exe) supplied with the .NET Framework SDK version 2.0 overcomes this limitation. Permcalc.exe walks through an assembly and provides an estimate of the permissions the assembly requires to run, regardless of whether they are declarative or imperative.
■Note The Permissions View tool (Permview.exe) is not supplied with the .NET Framework SDK version 2.0. Permview.exe from previous versions of the .NET Framework do not work correctly with .NET 2.0 assemblies. This is unfortunate, as Permcalc.exe does not provide a direct replacement for some of the useful functionality provided by Permview.exe. Although Permcalc.exe can determine both the imperative and declarative demands an assembly makes, it does not report the minimal, optional, and refusal requests made within an assembly.
The Code
The following example shows a class that declares a minimum, optional, and refusal request, as well as a number of imperative security demands.
using System; using System.Net;
using System.Security.Permissions;
//Minimum permission request for SocketPermission. [assembly: SocketPermission(SecurityAction.RequestMinimum,
Unrestricted = true)]
//Optional permission request for IsolatedStorageFilePermission. [assembly: IsolatedStorageFilePermission(SecurityAction.RequestOptional,
Unrestricted = true)]
//Refuse request for ReflectionPermission.
[assembly: ReflectionPermission(SecurityAction.RequestRefuse, Unrestricted = true)]
namespace Apress.VisualCSharpRecipes.Chapter11
{
class Recipe11_06
C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y |
405 |
{
public static void Main()
{
//Create and configure a FileIOPermission object that represents
//write access to the C:\Data folder.
FileIOPermission fileIOPerm =
new FileIOPermission(FileIOPermissionAccess.Write, @"C:\Data");
//Make the demand. fileIOPerm.Demand();
//Do something. . .
//Wait to continue.
Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();
}
}
}
Usage
Executing the command permview Recipe11-06.exe will generate the following output. Although this output is not particularly user-friendly, you can decipher it to determine the declarative permission requests made by an assembly. Each of the three types of permission requests—minimum, optional, and refused—is listed under a separate heading and is structured as the XML representation of a System.Security.PermissionSet object.
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.Net.SocketPermission, System, 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.IsolatedStorageFilePermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c5 61934e089" version="1" Unrestricted="true"/>
</PermissionSet>
refused permission set:
<PermissionSet class="System.Security.PermissionSet" version="1"> <IPermission class="System.Security.Permissions.ReflectionPermission,
mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c5 61934e089" version="1" Unrestricted="true"/>
</PermissionSet>
406 C H A P T E R 1 1 ■ S E C U R I T Y A N D C RY P TO G R A P H Y
Executing the command permcalc –sandbox Recipe11-06.exe will generate a file named sandbox.PermCalc.xml that contains XML representations of the permissions required by the assembly. Where the exact requirements of a permission cannot be determined (because it is based on runtime data), Permcalc.exe reports that unrestricted permissions of that type are required. You can instead default to the Internet zone permissions using the –Internet flag. Here are the contents of sandbox.PermCalc.xml when run against the sample code.
<?xml version="1.0"?> <Sandbox>
<PermissionSet version="1" class="System.Security.PermissionSet"> <IPermission Write="C:\Data" version="1"
class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<IPermission version="1" class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Flags="Execution" />
<IPermission version="1" class="System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Unrestricted="true" />
<IPermission version="1" class="System.Net.SocketPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Unrestricted="true" />
</PermissionSet>
</Sandbox>
11-7. Determine at Runtime If Your Code Has a Specific Permission
Problem
You need to determine at runtime if your assembly has a specific permission.
Solution
Instantiate and configure the permission you want to test for, and then pass it as an argument to the static method IsGranted of the class System.Security.SecurityManager.
How It Works
Using minimum permission requests, you can ensure that the runtime grants your assembly a specified set of permissions. As a result, when your code is running, you can safely assume that it has the requested minimum permissions. However, you might want to implement opportunistic functionality that your application offers only if the runtime grants your assembly appropriate permissions. This approach is partially formalized using optional permission requests, which allow you to define a set of permissions that your code could use if the security policy granted them, but are not essential for the successful operation of your code. (Recipe 11-5 provides more details on using optional permission requests.)
The problem with optional permission requests is that the runtime has no ability to communicate to your assembly which of the requested optional permissions it has granted. You can try to use a protected operation and fail gracefully if the call results in the exception