SafeHandle Classe

Definição

Representa uma classe wrapper para identificadores do sistema operacional. Essa classe deve ser herdada.

public ref class SafeHandle abstract : IDisposable
public ref class SafeHandle abstract : System::Runtime::ConstrainedExecution::CriticalFinalizerObject, IDisposable
[System.Security.SecurityCritical]
public abstract class SafeHandle : IDisposable
public abstract class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable
[System.Security.SecurityCritical]
public abstract class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable
[<System.Security.SecurityCritical>]
type SafeHandle = class
    interface IDisposable
type SafeHandle = class
    inherit CriticalFinalizerObject
    interface IDisposable
[<System.Security.SecurityCritical>]
type SafeHandle = class
    inherit CriticalFinalizerObject
    interface IDisposable
Public MustInherit Class SafeHandle
Implements IDisposable
Public MustInherit Class SafeHandle
Inherits CriticalFinalizerObject
Implements IDisposable
Herança
SafeHandle
Herança
Derivado
Atributos
Implementações

Exemplos

O exemplo de código a seguir cria um identificador de segurança personalizado para um identificador de arquivo do sistema operacional, derivado de SafeHandleZeroOrMinusOneIsInvalid. Ele lê bytes de um arquivo e exibe seus valores hexadecimal. Ele também contém um cinto de teste de falha que faz com que o thread seja anulado, mas o valor do identificador é liberado. Ao usar um IntPtr identificador para representar, o identificador ocasionalmente é vazado devido ao anulação de thread assíncrono.

Você precisará de um arquivo de texto na mesma pasta que o aplicativo compilado. Supondo que você nomeie o aplicativo "HexViewer", o uso da linha de comando é:

HexViewer <filename> -Fault

Opcionalmente, especifique -Fault para tentar vazar intencionalmente o identificador anulando o thread em uma determinada janela. Use a ferramenta Windows Perfmon.exe para monitorar contagens de identificadores durante a injeção de falhas.

using System;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;
using System.Security;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using System.Runtime.ConstrainedExecution;
using System.Security.Permissions;

namespace SafeHandleDemo
{
    internal class MySafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        // Create a SafeHandle, informing the base class
        // that this SafeHandle instance "owns" the handle,
        // and therefore SafeHandle should call
        // our ReleaseHandle method when the SafeHandle
        // is no longer in use.
        private MySafeFileHandle()
            : base(true)
        {
        }
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        override protected bool ReleaseHandle()
        {
            // Here, we must obey all rules for constrained execution regions.
            return NativeMethods.CloseHandle(handle);
            // If ReleaseHandle failed, it can be reported via the
            // "releaseHandleFailed" managed debugging assistant (MDA).  This
            // MDA is disabled by default, but can be enabled in a debugger
            // or during testing to diagnose handle corruption problems.
            // We do not throw an exception because most code could not recover
            // from the problem.
        }
    }

    [SuppressUnmanagedCodeSecurity()]
    internal static class NativeMethods
    {
        // Win32 constants for accessing files.
        internal const int GENERIC_READ = unchecked((int)0x80000000);

        // Allocate a file object in the kernel, then return a handle to it.
        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
        internal extern static MySafeFileHandle CreateFile(String fileName,
           int dwDesiredAccess, System.IO.FileShare dwShareMode,
           IntPtr securityAttrs_MustBeZero, System.IO.FileMode dwCreationDisposition,
           int dwFlagsAndAttributes, IntPtr hTemplateFile_MustBeZero);

        // Use the file handle.
        [DllImport("kernel32", SetLastError = true)]
        internal extern static int ReadFile(MySafeFileHandle handle, byte[] bytes,
           int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

        // Free the kernel's file object (close the file).
        [DllImport("kernel32", SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal extern static bool CloseHandle(IntPtr handle);
    }

    // The MyFileReader class is a sample class that accesses an operating system
    // resource and implements IDisposable. This is useful to show the types of
    // transformation required to make your resource wrapping classes
    // more resilient. Note the Dispose and Finalize implementations.
    // Consider this a simulation of System.IO.FileStream.
    public class MyFileReader : IDisposable
    {
        // _handle is set to null to indicate disposal of this instance.
        private MySafeFileHandle _handle;

        public MyFileReader(String fileName)
        {
            // Security permission check.
            String fullPath = Path.GetFullPath(fileName);
            new FileIOPermission(FileIOPermissionAccess.Read, fullPath).Demand();

            // Open a file, and save its handle in _handle.
            // Note that the most optimized code turns into two processor
            // instructions: 1) a call, and 2) moving the return value into
            // the _handle field.  With SafeHandle, the CLR's platform invoke
            // marshaling layer will store the handle into the SafeHandle
            // object in an atomic fashion. There is still the problem
            // that the SafeHandle object may not be stored in _handle, but
            // the real operating system handle value has been safely stored
            // in a critical finalizable object, ensuring against leaking
            // the handle even if there is an asynchronous exception.

            MySafeFileHandle tmpHandle;
            tmpHandle = NativeMethods.CreateFile(fileName, NativeMethods.GENERIC_READ,
                FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

            // An async exception here will cause us to run our finalizer with
            // a null _handle, but MySafeFileHandle's ReleaseHandle code will
            // be invoked to free the handle.

            // This call to Sleep, run from the fault injection code in Main,
            // will help trigger a race. But it will not cause a handle leak
            // because the handle is already stored in a SafeHandle instance.
            // Critical finalization then guarantees that freeing the handle,
            // even during an unexpected AppDomain unload.
            Thread.Sleep(500);
            _handle = tmpHandle;  // Makes _handle point to a critical finalizable object.

            // Determine if file is opened successfully.
            if (_handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error(), fileName);
        }

        public void Dispose()  // Follow the Dispose pattern - public nonvirtual.
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        // No finalizer is needed. The finalizer on SafeHandle
        // will clean up the MySafeFileHandle instance,
        // if it hasn't already been disposed.
        // However, there may be a need for a subclass to
        // introduce a finalizer, so Dispose is properly implemented here.
        protected virtual void Dispose(bool disposing)
        {
            // Note there are three interesting states here:
            // 1) CreateFile failed, _handle contains an invalid handle
            // 2) We called Dispose already, _handle is closed.
            // 3) _handle is null, due to an async exception before
            //    calling CreateFile. Note that the finalizer runs
            //    if the constructor fails.
            if (_handle != null && !_handle.IsInvalid)
            {
                // Free the handle
                _handle.Dispose();
            }
            // SafeHandle records the fact that we've called Dispose.
        }

        public byte[] ReadContents(int length)
        {
            if (_handle.IsInvalid)  // Is the handle disposed?
                throw new ObjectDisposedException("FileReader is closed");

            // This sample code will not work for all files.
            byte[] bytes = new byte[length];
            int numRead = 0;
            int r = NativeMethods.ReadFile(_handle, bytes, length, out numRead, IntPtr.Zero);
            // Since we removed MyFileReader's finalizer, we no longer need to
            // call GC.KeepAlive here.  Platform invoke will keep the SafeHandle
            // instance alive for the duration of the call.
            if (r == 0)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (numRead < length)
            {
                byte[] newBytes = new byte[numRead];
                Array.Copy(bytes, newBytes, numRead);
                bytes = newBytes;
            }
            return bytes;
        }
    }

    static class Program
    {
        // Testing harness that injects faults.
        private static bool _printToConsole = false;
        private static bool _workerStarted = false;

        private static void Usage()
        {
            Console.WriteLine("Usage:");
            // Assumes that application is named HexViewer"
            Console.WriteLine("HexViewer <fileName> [-fault]");
            Console.WriteLine(" -fault Runs hex viewer repeatedly, injecting faults.");
        }

        private static void ViewInHex(Object fileName)
        {
            _workerStarted = true;
            byte[] bytes;
            using (MyFileReader reader = new MyFileReader((String)fileName))
            {
                bytes = reader.ReadContents(20);
            }  // Using block calls Dispose() for us here.

            if (_printToConsole)
            {
                // Print up to 20 bytes.
                int printNBytes = Math.Min(20, bytes.Length);
                Console.WriteLine("First {0} bytes of {1} in hex", printNBytes, fileName);
                for (int i = 0; i < printNBytes; i++)
                    Console.Write("{0:x} ", bytes[i]);
                Console.WriteLine();
            }
        }

        static void Main(string[] args)
        {
            if (args.Length == 0 || args.Length > 2 ||
                args[0] == "-?" || args[0] == "/?")
            {
                Usage();
                return;
            }

            String fileName = args[0];
            bool injectFaultMode = args.Length > 1;
            if (!injectFaultMode)
            {
                _printToConsole = true;
                ViewInHex(fileName);
            }
            else
            {
                Console.WriteLine("Injecting faults - watch handle count in perfmon (press Ctrl-C when done)");
                int numIterations = 0;
                while (true)
                {
                    _workerStarted = false;
                    Thread t = new Thread(new ParameterizedThreadStart(ViewInHex));
                    t.Start(fileName);
                    Thread.Sleep(1);
                    while (!_workerStarted)
                    {
                        Thread.Sleep(0);
                    }
                    t.Abort();  // Normal applications should not do this.
                    numIterations++;
                    if (numIterations % 10 == 0)
                        GC.Collect();
                    if (numIterations % 10000 == 0)
                        Console.WriteLine(numIterations);
                }
            }
        }
    }
}

Comentários

A SafeHandle classe fornece uma finalização crítica dos recursos de manipulação, impedindo que as alças sejam recuperadas prematuramente pela coleta de lixo e sejam recicladas por Windows para referenciar objetos não gerenciados não intencionais.

Este tópico inclui as seções a seguir:

Por que SafeHandle?
O que o SafeHandle faz
Classes derivadas de SafeHandle

Por que SafeHandle?

Antes do .NET Framework versão 2.0, todos os identificadores do sistema operacional só podiam ser encapsulados no IntPtr objeto wrapper gerenciado. Embora essa fosse uma maneira conveniente de interoperar com código nativo, os identificadores poderiam ser vazados por exceções assíncronas, como um thread abortando inesperadamente ou um estouro de pilha. Essas exceções assíncronas são um obstáculo para limpar os recursos do sistema operacional e podem ocorrer quase em qualquer lugar do seu aplicativo.

Embora as substituições ao método permitam a Object.Finalize limpeza de recursos não gerenciados quando um objeto está sendo coletado, em algumas circunstâncias, objetos finalizáveis podem ser recuperados pela coleta de lixo enquanto executam um método dentro de uma chamada de invocação de plataforma. Se um finalizador liberar o identificador passado para essa chamada de invocação de plataforma, isso poderá levar a lidar com a corrupção. O identificador também pode ser recuperado enquanto seu método é bloqueado durante uma chamada de invocação de plataforma, como ao ler um arquivo.

Mais criticamente, como Windows recicla agressivamente identificadores, um identificador pode ser reciclado e apontar para outro recurso que possa conter dados confidenciais. Isso é conhecido como um ataque de reciclagem e pode potencialmente corromper dados e ser uma ameaça à segurança.

O que o SafeHandle faz

A SafeHandle classe simplifica vários desses problemas de tempo de vida do objeto e é integrada à invocação de plataforma para que os recursos do sistema operacional não sejam vazados. A SafeHandle classe resolve problemas de tempo de vida do objeto atribuindo e liberando identificadores sem interrupção. Ele contém um finalizador crítico que garante que o identificador esteja fechado e tenha a garantia de ser executado durante descarregamentos inesperados AppDomain , mesmo nos casos em que a chamada de invocação da plataforma é considerada em um estado corrompido.

Como SafeHandle herda, CriticalFinalizerObjecttodos os finalizadores não críticos são chamados antes de qualquer um dos finalizadores críticos. Os finalizadores são chamados em objetos que não estão mais ativos durante a mesma passagem de coleta de lixo. Por exemplo, um FileStream objeto pode executar um finalizador normal para liberar dados em buffer existentes sem o risco de o identificador ser vazado ou reciclado. Essa ordenação muito fraca entre finalizadores críticos e não críticos não se destina ao uso geral. Existe principalmente para ajudar na migração de bibliotecas existentes, permitindo que essas bibliotecas usem SafeHandle sem alterar sua semântica. Além disso, o finalizador crítico e tudo o que ele chama, como o SafeHandle.ReleaseHandle() método, devem estar em uma região de execução restrita. Isso impõe restrições sobre qual código pode ser gravado no grafo de chamada do finalizador.

As operações de invocação de plataforma incrementam automaticamente a contagem de referência de identificadores encapsulados por um SafeHandle e as decrementam após a conclusão. Isso garante que o identificador não será reciclado ou fechado inesperadamente.

Você pode especificar a propriedade do identificador subjacente ao construir SafeHandle objetos fornecendo um valor para o ownsHandle argumento no construtor de SafeHandle classe. Isso controla se o SafeHandle objeto liberará o identificador depois que o objeto tiver sido descartado. Isso é útil para identificadores com requisitos de tempo de vida peculiares ou para consumir um identificador cujo tempo de vida é controlado por outra pessoa.

Classes derivadas de SafeHandle

SafeHandle é uma classe wrapper abstrata para identificadores do sistema operacional. A derivação dessa classe é difícil. Em vez disso, use as classes derivadas no espaço Microsoft.Win32.SafeHandles que fornecem manipuladores de segurança para o seguinte:

Notas aos Implementadores

Para criar uma classe derivada, SafeHandlevocê deve saber como criar e liberar um identificador do sistema operacional. Esse processo é diferente para diferentes tipos de identificador porque alguns usam a função CloseHandle , enquanto outros usam funções mais específicas, como UnmapViewOfFile ou FindClose. Por esse motivo, você deve criar uma classe derivada de cada tipo de identificador do SafeHandle sistema operacional que você deseja encapsular em um identificador seguro.

Ao herdar, SafeHandlevocê deve substituir os seguintes membros: IsInvalid e ReleaseHandle().

Você também deve fornecer um construtor público sem parâmetros que chama o construtor base com um valor que representa um valor de identificador inválido e um Boolean valor que indica se o identificador nativo pertence ao SafeHandle e, consequentemente, deve ser liberado quando ele SafeHandle foi descartado.

Construtores

SafeHandle()
SafeHandle(IntPtr, Boolean)

Inicializa uma nova instância da classe SafeHandle com o valor do identificador inválido especificado.

Campos

handle

Especifica o identificador a ser encapsulado.

Propriedades

IsClosed

Obtém um valor que indica se o identificador está fechado.

IsInvalid

Quando substituído em uma classe derivada, obtém um valor que indica se o valor de indicador é válido.

Métodos

Close()

Marca o identificador de liberação de recursos.

DangerousAddRef(Boolean)

Incrementa manualmente o contador de referências em instâncias do SafeHandle.

DangerousGetHandle()

Retorna o valor do campo handle.

DangerousRelease()

Diminui manualmente o contador de referência em uma instância SafeHandle.

Dispose()

Libera todos os recursos usados pela classe SafeHandle.

Dispose(Boolean)

Libera os recursos não gerenciados usados pela classe SafeHandle que especifica se deve executar uma operação de descarte normal.

Equals(Object)

Determina se o objeto especificado é igual ao objeto atual.

(Herdado de Object)
Finalize()

Libera todos os recursos associados ao identificador.

GetHashCode()

Serve como a função de hash padrão.

(Herdado de Object)
GetType()

Obtém o Type da instância atual.

(Herdado de Object)
MemberwiseClone()

Cria uma cópia superficial do Object atual.

(Herdado de Object)
ReleaseHandle()

Quando substituído em uma classe derivada, executa o código necessário para liberar o identificador.

SetHandle(IntPtr)

Define o identificador para o identificador pré-existente especificado.

SetHandleAsInvalid()

Marca um identificador como não mais usado.

ToString()

Retorna uma cadeia de caracteres que representa o objeto atual.

(Herdado de Object)

Aplica-se a

Confira também