Оптимизация безопасности

Проверки безопасности могут приводить к проблемам производительности в некоторых приложениях. Есть два способа оптимизации, которые могут использоваться для повышения производительности. В одном из них требования безопасности объединяются; в другом подавляются требования разрешений на доступ к неуправляемому коду. При том, что эти способы могут повысить производительность приложения, они также могут сделать приложение более уязвимым в плане безопасности. Прежде чем использовать эти методы оптимизации, необходимо принять следующие меры предосторожности.

  • Используйте при работе с управляемым кодом Правила написания безопасного кода.

  • Понимать последствия оптимизации для безопасности и использовать другие методы обеспечения безопасности приложения там, где это применимо.

  • Реализовать минимальное количество оптимизаций безопасности, необходимое для улучшения производительности приложения.

После оптимизации кода необходимо протестировать оптимизированный код, чтобы определить, действительно ли его производительность улучшилась. Если этого не произошло, необходимо снять оптимизацию безопасности во избежание неумышленного ослабления безопасности.

Предупреждение

Оптимизация безопасности требует изменения стандартного управления доступом для кода.Во избежание внесения уязвимых фрагментов в код необходимо ясно осознавать последствия используемых методов оптимизации для безопасности, прежде чем их использовать.

Объединение требований безопасности

Для оптимизации кода, осуществляющего запросы безопасности, можно в некоторых ситуациях применять способ объединения требований.

Например, если:

  • код выполняет несколько операций в одном методе, и

  • при выполнении каждой из этих операций код вызывает управляемую библиотеку классов, требующую от него одних и тех же разрешений при каждом вызове библиотеки,

то:

  • можно модифицировать код таким образом, чтобы он выполнял Demand и Assert для этого разрешения с целью снизить дополнительные издержки, создаваемые требованиями безопасности.

Если глубина стека вызовов над этим методом велика, применение данного способа может привести к значительному выигрышу в производительности.

Чтобы увидеть, как это работает, предположим, что метод 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

Основные понятия

Написание безопасных библиотек классов

Другие ресурсы

Управление доступом для кода