安全性最佳化

安全性檢查會造成某些應用程式的效能問題。 您可以使用兩種最佳化技術來改善效能。 其中一種技術會結合安全性要求,而另一種則會隱藏呼叫進入 Unmanaged 程式碼的使用權限要求。 雖然這些技術或許可以改善應用程式的效能,但是它們可能也會讓應用程式變得容易遭受安全性危害的侵襲。 在您使用這些最佳化技術之前,請先採取下列防範措施:

  • 遵循 Managed 程式碼的安全程式碼撰寫方針

  • 暸解最佳化的安全性含意並使用其他方法提供應用程式的適當保護。

  • 實作改善應用程式效能所需的最低安全性最佳化。

最佳化程式碼後,您應該測試已最佳化的程式碼,以確定它的效能是否確實已經改善。 如果沒有改善,您就應該移除安全性最佳化,以利預防安全性意外減弱。

警告

進行安全性最佳化時,您必須變更標準的程式碼存取安全性。為避免造成您的程式碼安全性不足的問題,在您使用最佳化技術之前,請務必先暸解它們的安全性含意。

結合安全性要求

若要產生安全性要求的最佳化程式碼,您可以在某些情形下使用結合需求的方法。

例如,當

  • 您的程式碼執行單一方法內的許多作業,以及

  • 執行每一項作業時,您的程式碼會呼叫進入 Managed 類別庫內,要求您的程式碼需擁有和每一項類別庫呼叫相同的使用權限

然後:

  • 您可以修改程式碼以執行該使用權限的 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.

保留 Unmanaged 程式碼使用權限的要求

另一種特殊的最佳化方法是用於擁有呼叫 Unmanaged 程式碼使用權限的程式碼。 這種最佳化方法可以使您的 Managed 程式碼呼叫進入 Unmanaged 程式碼,而不會過度耗用堆疊查核行程的時間。 判斷 Unmanaged 程式碼使用權限可以縮短堆疊查核行程的時間,但在這個主題中所描述的最佳化方法卻可以完全去除耗用的時間 (如需呼叫進入 Unmanaged 程式碼使用權限的詳細資訊,請參閱 SecurityPermission)。

一般情形下,呼叫進入 Unmanaged 程式碼會引發 Unmanaged 程式碼使用權限的要求,而產生堆疊查核行程以決定所有呼叫端是否擁有呼叫進入 Unmanaged 程式碼的使用權限。 將自訂屬性 SuppressUnmanagedCodeSecurityAttribute 套用至呼叫進入 Unmanaged 程式碼的方法可以隱藏該要求。 這個屬性將取代 Run Time 時的完整堆疊查核行程,而只驗證在連結時間即時呼叫端的使用權限。 實際上,使用這個屬性可以建立一個進入 Unmanaged 程式碼的入門點。 只有擁有 Unmanaged 程式碼使用權限的程式碼才可使用這個屬性,否則,它將不產生任何作用。

警告

使用 SuppressUnmanagedCodeSecurityAttribute 屬性時必須非常謹慎。不正確地使用這個屬性將危害到安全性。SuppressUnmanagedCodeSecurityAttribute 屬性不應該用於允許較低信任程度的程式碼 (不擁有 Unmanaged 程式碼使用權限的程式碼) 呼叫進入 Unmanaged 程式碼。

這個屬性最適合套用至私用宣告的 Unmanaged 程式碼進入點 (Entry Point),使其他組件中的程式碼無法存取並從安全性的隱藏獲得益處。 一般而言,使用這個屬性的高度受信任 Managed 程式碼在叫用代表呼叫端上的 Unmanaged 程式碼之前,會先要求某些呼叫端的使用權限。

以下的範例將說明套用至私用進入點的 SuppressUnmanagedCodeSecurityAttribute 屬性。

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

只有極少數的 Unmanaged 程式碼能在所有的情況下是完全安全的,如果將具有 SuppressUnmanagedCodeSecurityAttribute 屬性的方法設為公用 (Public) 而非私用 (Private),它將直接公開於其他的 Managed 程式碼。 如果您選擇公開具有 SuppressUnmanagedCodeSecurityAttribute 屬性的方法,這個 Unmanaged 程式碼不僅在功能上必須是安全的,而且必須不受惡意呼叫端的攻擊影響。 例如,程式碼必須能適當地執行,即使在傳遞錯誤的引數而使程式碼無法正常作業的情況下也必須如此。

使用宣告式覆寫與命令式要求

判斷和其他覆寫是設定宣告式最快速的方式,而要求則是設定命令式最快速的方式。 雖然並不能立即提升執行的效能,但使用宣告式覆寫和命令式要求仍可以協助您增進程式碼的執行效能。

請參閱

參考

File.GetCreationTime

SecurityPermission

SuppressUnmanagedCodeSecurityAttribute

概念

撰寫安全類別庫

其他資源

程式碼存取安全性