Security Optimizations
Security checks can cause performance problems for some applications. There are two optimization techniques that you can use to improve performance. One technique combines security demands; the other suppresses demands for permission to call into unmanaged code. Although these techniques might improve the performance of your application, they can also make your application vulnerable to security exploits. Before using these optimization techniques, you should take the following precautions:
Follow the Secure Coding Guidelines for managed code.
Understand the security implications of the optimizations and use other methods to protect your application as appropriate.
Implement the minimum security optimizations required to improve application performance.
After optimizing your code, you should test the optimized code to determine whether its performance actually improved. If not, you should remove the security optimizations to help prevent inadvertent security weaknesses.
Caution |
---|
Security optimization requires you to change the standard code access security. To avoid introducing security vulnerabilities into your code, be sure you understand the security implications of the optimization techniques before using them. |
Combining Security Demands
To optimize code that makes security demands, you can, in some situations, use a technique for combining demands.
For example, if:
your code performs a number of operations within a single method, and
when performing each of those operations, your code calls into a managed class library that demands that your code have the same permission on each call to the library,
then:
- you can modify your code to perform a Demand and an Assert of that permission to reduce the overhead incurred by the security demands.
If the call stack depth above the method is large, using this technique can result in a significant performance gain.
To illustrate how this works, suppose method M performs 100 operations. Each operation calls into a library that makes a security demand requiring your code and all its callers to have the X permission. Because of the security demands, each operation causes the runtime to walk the entire call stack examining each caller's permissions, to determine whether X permission was actually granted to each caller. If the call stack above method M is n levels deep, 100n comparisons are required.
To optimize, you can do the following in method M:
Demand X, which results in the runtime performing a stack walk (of depth n) to ensure that all callers indeed have permission X.
Then, assert permission X, which causes subsequent stack walks to stop at method M and succeed, thereby reducing the number of permission comparisons by 99n.
In the following code example, the GetFileCreationTime method takes a string representation of a directory as a parameter and displays the name and creation date of every file in that directory. The static File.GetCreationTime method reads information from the files but requires a demand and stack walk for every file it reads. The method creates a new instance of the FileIOPermission object, performs a demand to check the permissions of all callers on the stack, and then asserts the permission if the demand is successful. If the demand succeeds, only one stack walk is performed and the method reads the creation time from every file in the passed directory.
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
namespace OptimizedSecurity
{
public class FileUtil
{
public FileUtil()
{
}
public void GetFileCreationTime(string Directory)
{
//Initialize DirectoryInfo object to the passed directory.
DirectoryInfo DirFiles = new DirectoryInfo(Directory);
//Create a DateTime object to be initialized below.
DateTime TheTime;
//Get a list of files for the current directory.
FileInfo[] Files = DirFiles.GetFiles();
//Create a new instance of FileIOPermission with read
//permission to the current directory.
FileIOPermission FilePermission = new FileIOPermission(FileIOPermissionAccess.Read, Directory);
try
{
//Check the stack by making a demand.
FilePermission.Demand();
//If the demand succeeded, assert permission and
//perform the operation.
FilePermission.Assert();
for(int x = 0 ; x<= Files.Length -1 ; x++)
{
TheTime = File.GetCreationTime(Files[x].FullName);
Console.WriteLine("File: {0} Created: {1:G}", Files[x].Name,TheTime );
}
// Revert the Assert when the operation is complete.
CodeAccessPermission.RevertAssert();
}
//Catch a security exception and display an error.
catch(SecurityException)
{
Console.WriteLine("You do not have permission to read this directory.");
}
}
}
}
Option Explicit
Option Strict
Imports System
Imports System.IO
Imports System.Security
Imports System.Security.Permissions
Namespace OptimizedSecurity
Public Class FileUtil
Public Sub New()
End Sub
Public Sub GetFileCreationTime(directory As String)
'Initialize DirectoryInfo object to the passed directory.
Dim dirFiles As New DirectoryInfo(directory)
'Create a DateTime object to be initialized below.
Dim theTime As DateTime
'Get a list of files for the current directory.
Dim files As FileInfo() = dirFiles.GetFiles()
'Create a new instance of FileIOPermission with read
'permission to the current directory.
Dim filePermission As New FileIOPermission(FileIOPermissionAccess.Read, Directory)
Try
'Check the stack by making a demand.
filePermission.Demand()
'If the demand succeeded, assert permission and
'perform the operation.
filePermission.Assert()
Dim x As Integer
For x = 0 To Files.Length - 1
theTime = file.GetCreationTime(files(x).FullName)
Console.WriteLine("File: {0} Created: {1:G}", files(x).Name, theTime)
Next x
' Revert the Assert when the operation is complete.
CodeAccessPermission.RevertAssert()
'Catch a security exception and display an error.
Catch
Console.WriteLine("You do not have permission to read this directory.")
End Try
End Sub
End Class
End Namespace
If the demand in the previous example succeeds, every file and its creation date and time are displayed for the passed directory. If the demand fails, the security exception is intercepted and the following message is displayed to the console:
You do not have permission to read this directory.
Suppressing Demands for Unmanaged Code Permission
A special optimization is available to code that has permission to call unmanaged code. This optimization enables your managed code to call into unmanaged code without incurring the overhead of a stack walk. Asserting unmanaged code permission can shorten the stack walk, but the optimization described in this topic can eliminate it entirely. (See SecurityPermission for more information about the permission to call into unmanaged code.)
Normally, a call into unmanaged code raises a demand of unmanaged code permission, which results in a stack walk that determines whether all callers have permission to call into unmanaged code. Applying the custom attribute SuppressUnmanagedCodeSecurityAttribute to the method that calls into unmanaged code suppresses the demand. This attribute replaces the full stack walk at run time with a check that only verifies the permissions of the immediate caller at link time. In effect, using this attribute creates an open door into unmanaged code. Only code that has unmanaged code permission can use this attribute; otherwise, it has no effect.
Caution |
---|
Use the SuppressUnmanagedCodeSecurityAttribute attribute only with extreme care. Incorrect use of this attribute can create security weaknesses. The SuppressUnmanagedCodeSecurityAttribute attribute should never be used to allow less-trusted code (code that does not have unmanaged code permission) to call into unmanaged code. |
This attribute is best applied only to privately declared entry points to unmanaged code so that code in other assemblies cannot access and take advantage of the security suppression. Typically, the highly trusted managed code that uses this attribute first demands some permission of callers before invoking the unmanaged code on the caller's behalf.
The following example shows the SuppressUnmanagedCodeSecurityAttribute attribute applied to a private entry point.
<SuppressUnmanagedCodeSecurityAttribute()> Private Declare Sub
EntryPoint Lib "some.dll"(args As String)
[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("some.dll")]
private static extern void EntryPoint(string args);
In the rare case of unmanaged code that is completely safe for all possible circumstances, a method with the SuppressUnmanagedCodeSecurityAttribute attribute can be exposed to other managed code directly by making it public instead of private. If you choose to expose a method that has the SuppressUnmanagedCodeSecurityAttribute attribute, the functionality of the unmanaged code must be not only safe but also impervious to attack by malicious callers. For example, the code must operate appropriately even when unintended arguments are fabricated to specifically cause the code to malfunction.
Using Declarative Overrides and Imperative Demands
Asserts and other overrides are fastest when made declaratively, while demands are fastest when made imperatively. Although the performance gains might not be dramatic, using declarative overrides and imperative demands can help you improve code performance.
See Also
Reference
SuppressUnmanagedCodeSecurityAttribute
Concepts
Writing Secure Class Libraries