安全优化

更新:2007 年 11 月

对于某些应用程序,安全检查会导致性能问题。您可以使用两种优化方法来提高性能。一种方法合并安全要求;而另一种方法取消对调用非托管代码时所需权限的要求。虽然这些方法可以提高应用程序的性能,但它们也会使您的应用程序的安全性降低。在使用这些优化方法之前,您应该采取以下预防措施:

  • 对于托管代码遵循代码安全维护指南

  • 了解进行优化可能带来的安全问题并相应地使用其他方法保护您的应用程序。

  • 实施最少的必需安全优化操作来提高应用程序的性能。

在优化代码之后,您应该对经过优化的代码进行测试以确定其性能是否得到实际的提高。如果没有,您应该移除安全优化以帮助防止无意中造成安全缺陷。

警告:

安全优化要求您更改标准的代码访问安全性。为了避免将安全脆弱性带入您的代码,在使用优化方法之前一定要确保您了解这些方法可能带来的安全问题。

合并安全要求

若要优化发出安全要求的代码,在某些情况下可以使用合并要求的方法。

例如,如果:

  • 您的代码在一个方法中执行许多操作,并且

  • 在执行这些操作中的每个操作时,您的代码均调用一个托管类库,该类库要求您的代码在每次调用该库时拥有同一权限。

然后:

  • 可以修改您的代码来执行该权限的 DemandAssert,以减轻因安全要求而导致的系统开销。

如果方法上的调用堆栈深度较深,则使用此方法可使性能大大改善。

为阐释其工作原理,假定方法 M 执行 100 个操作。每个操作均调用一个库,该库发出一个安全要求,它要求您的代码及其所有调用方均拥有 X 权限。由于此安全要求,每个操作均会使运行时遍历整个调用堆栈来检查每个调用方的权限,以确定是否实际向每个调用方均授予了 X 权限。如果方法 M 上的调用堆栈有 n 层深,则需要进行 100n 次比较。

若要优化,可在方法 M 中执行以下操作:

  • 要求 X,从而使运行时执行堆栈审核(深度 n)来确保所有调用方确实拥有权限 X。

  • 然后,断言权限 X,这使随后的堆栈审核在方法 M 处停止并成功,从而减少了 99n 次权限比较操作。

在下面的代码示例中,GetFileCreationTime 方法采用一个目录的字符串表示形式作为参数,显示该目录中每个文件的名称和创建日期。静态 File.GetCreationTime 方法读取文件中的信息,但对它读取的每个文件,都需要一个要求并进行一次堆栈审核。该方法创建 FileIOPermission 对象的一个新实例,执行一个要求来检查堆栈上所有调用方的权限;然后如果要求成功,就断言该权限。如果要求成功,则仅执行一次堆栈审核,该方法读取传递的目录中每个文件的创建时间。

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

如果上述示例中的要求成功,则会显示所传递的目录中的每个文件及其创建日期和时间。如果该要求失败,则会截获安全性异常,并会在控制台上显示下面的消息:

You do not have permission to read this directory.

取消对非托管代码权限的要求

为拥有调用非托管代码权限的代码提供了一种特殊的优化。此优化使您的托管代码能够调用非托管代码而不会引起执行堆栈审核的系统开销。断言非托管代码权限可以缩短堆栈审核,但是此主题中描述的优化可完全消除堆栈审核。(有关调入非托管代码的权限的更多信息,请参见 SecurityPermission。)

通常,调用非托管代码会引发对非托管代码权限的要求,它导致执行堆栈审核,确定是否所有调用方都有权限调用非托管代码。如果将自定义属性 SuppressUnmanagedCodeSecurityAttribute 应用到调入非托管代码的方法,则会取消该要求。此属性将运行时的完整堆栈审核替换为一个检查,该检查仅在链接时验证直接调用方的权限。实际上,使用此属性将创建进入非托管代码的开放的门。只有拥有非托管代码权限的代码才能使用此属性;其他情况下,此属性无效。

警告:

使用 SuppressUnmanagedCodeSecurityAttribute 属性必须十分谨慎。使用此属性不当会造成安全缺陷。绝对不要使用 SuppressUnmanagedCodeSecurityAttribute 属性来允许受信程度较低的代码(没有非托管代码权限的代码)调用非托管代码。

此属性最好只应用于非托管代码的私下声明的入口点,从而使其他程序集内的代码不能访问及利用安全设置取消进行操作。通常,使用此属性的、高度信任的托管代码首先向调用方请求一些权限,然后代表调用方调用非托管代码。

下面的示例说明应用于私有入口点的 SuppressUnmanagedCodeSecurityAttribute 属性。

<SuppressUnmanagedCodeSecurityAttribute()> Private Declare Sub 
EntryPoint Lib "some.dll"(args As String)
[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("some.dll")]
private static extern void EntryPoint(string args);

在非托管代码对所有可能的情况都完全安全的情况下(这种情况极少见),通过使具有 SuppressUnmanagedCodeSecurityAttribute 属性的方法成为公共方法而非私有方法,可直接使该方法对其他托管代码公开。如果选择公开具有 SuppressUnmanagedCodeSecurityAttribute 属性的方法,则不但非托管代码的功能必须安全,而且还必须能够经得住恶意调用方的攻击。例如,即使在编造非预期的参数特意使代码不能正常运行时,代码也必须安全运行。

使用声明式重写和强制性要求

断言和其他重写为声明形式时速度最快,而要求为强制形式时速度最快。尽管性能提高可能不大,使用声明式重写和强制性要求可帮助您改善代码性能。

请参见

概念

编写安全类库

参考

File.GetCreationTime

SecurityPermission

SuppressUnmanagedCodeSecurityAttribute

其他资源

代码访问安全性