セキュリティの最適化

更新 : 2007 年 11 月

セキュリティ チェックは、アプリケーションによってはパフォーマンス上の問題を生じることがあります。パフォーマンスを改善するために、2 つの最適化の手法があります。第 1 の方法では複数のセキュリティ要求を組み合わせ、第 2 の方法ではアンマネージ コードを呼び出すためのアクセス許可に対する要求を中止します。これらの手法はアプリケーションのパフォーマンスを改善しますが、セキュリティを攻略される脆弱性をアプリケーションに与えることになります。これらの最適化の手法を使用する前に、次の対策を講じる必要があります。

  • マネージ コードの安全なコーディングのガイドラインに従います。

  • 最適化のセキュリティについて理解し、他の適切な方法を使用してアプリケーションを保護します。

  • アプリケーションのパフォーマンス改善に必要な、最小限のセキュリティ最適化を実装します。

コードの最適化の後で、最適化されたコードをテストし、パフォーマンスが実際に改善されたかどうかを判断します。パフォーマンスが改善されていない場合は、不用意なセキュリティ脆弱性を防ぐことができるように、セキュリティ最適化を取り除きます。

注意 :

セキュリティ最適化を実行するには、標準のコード アクセス セキュリティの変更が必要になります。コードにセキュリティ脆弱性が入るのを防ぐために、最適化の手法を使用する前に、そのセキュリティに関連する事項をよく理解する必要があります。

セキュリティ確認要求の結合

セキュリティ確認要求を行うコードを最適化するには、場合によっては、確認要求を組み合わせる方法を利用できます。

たとえば、次のような場合を想定してみます。

  • コード内の 1 つのメソッドで多数の操作を実行する場合。

  • さらに、これらの操作を実行するときに、コードがマネージ クラス ライブラリに対して呼び出しを行い、これらの各呼び出しについて同じアクセス許可を持つことをそのライブラリが要求している場合。

このような場合には、次のような処置を行います。

  • 該当のアクセス許可に対して DemandAssert を行うようにコードを変更し、これらのセキュリティ確認要求によるオーバーヘッドを減らします。

コール スタックで、そのメソッドの上に多数の呼び出し元がある場合には、この方法を使用するとパフォーマンスが大幅に向上する可能性があります。

この方法のしくみを説明するために、たとえばメソッド M が 100 個の操作を実行するとします。各操作では、コードとそのすべての呼び出し元がアクセス許可 X を持つことを要求するセキュリティ確認要求を行うライブラリを呼び出します。このセキュリティ確認要求があるため、操作ごとにスタック ウォークが実行され、コール スタック内の各呼び出し元についてアクセス許可をチェックし、それぞれの呼び出し元にアクセス許可 X が与えられているかどうかが確認されます。メソッド M より上のコール スタックの深さが n レベルだとすると、100n 回の比較が行われることになります。

これを最適化するには、メソッド M で次の処置を行います。

  • X を要求 (確認要求) します。これにより、スタック ウォーク (深さ n) が実行され、すべての呼び出し元がアクセス許可 X を持つかどうかが確認されます。

  • 次に、アクセス許可 X をアサートします。これにより、それ以降のスタック ウォークがメソッド M のところで停止して成功するため、アクセス許可を比較する回数が 99n 回も減少します。

パラメータとしてディレクトリの文字列形式を受け取り、そのディレクトリ内の各ファイルの名前と作成日時を表示する GetFileCreationTime メソッドを次のコード例に示します。静的メソッド File.GetCreationTime はファイルから情報を読み取りますが、読み取り対象のすべてのファイルについて、確認要求とスタック ウォークを必要とします。このメソッドは FileIOPermission オブジェクトの新しいインスタンスを作成し、確認要求を実行してコール スタック上のすべての呼び出し元のアクセス許可をチェックし、確認要求が成功した場合はそのアクセス許可をアサートします。確認要求が成功すると、スタック ウォークが 1 回だけ実行され、メソッドは渡されたディレクトリ内の各ファイルから作成日時を読み取ります。

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

その他の技術情報

コード アクセス セキュリティ