SafeHandle 类
定义
重要
一些信息与预发行产品相关,相应产品在发行之前可能会进行重大修改。 对于此处提供的信息,Microsoft 不作任何明示或暗示的担保。
表示操作系统句柄的包装类。 必须继承此类。
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
- 继承
-
SafeHandle
- 继承
- 派生
- 属性
- 实现
示例
下面的代码示例为从中派生 SafeHandleZeroOrMinusOneIsInvalid的操作系统文件句柄创建自定义安全句柄。 它从文件读取字节并显示其十六进制值。 它还包含导致线程中止但句柄值被释放的错误测试工具。 使用表示 IntPtr 句柄时,由于异步线程中止,句柄偶尔会泄露。
需要与编译的应用程序位于同一文件夹中的文本文件。 假设将应用程序命名为“HexViewer”,命令行用法为:
HexViewer <filename> -Fault
(可选)指定 -Fault 有意尝试通过中止特定窗口中的线程来泄漏句柄。 使用 Windows Perfmon.exe 工具监视注入故障时的处理计数。
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);
}
}
}
}
}
注解
该 SafeHandle 类提供句柄资源的关键终结处理,防止句柄被垃圾回收机制过早回收,以及被操作系统回收后用于引用非预期的非托管对象。
为什么使用 SafeHandle?
尽管替代 Object.Finalize 方法允许在对对象进行垃圾回收时清理非托管资源,但在某些情况下,可以在平台调用中执行方法时通过垃圾回收来回收可终结的对象。 如果终结器释放了传递给该平台调用的句柄,则可能会导致句柄损坏。 在方法因平台调用(例如在读取文件时)被阻止的情况下,句柄也可能会被回收。
更重要的是,由于 Windows 积极回收句柄,一个句柄可能被回收并指向另一个可能包含敏感数据的资源。 这称为回收攻击,可能会损坏数据,并构成安全威胁。
SafeHandle 的作用
该 SafeHandle 类简化了其中几个对象生存期问题,并与平台调用集成,以避免操作系统资源泄露。 该 SafeHandle 类通过分配和释放句柄来解决对象生存期问题,而不会中断。 它包含一个关键终结器,可确保句柄已关闭,并保证在意外 AppDomain 卸载期间运行,即使在认为假定平台调用处于损坏状态的情况下也是如此。
由于 SafeHandle 继承自 CriticalFinalizerObject,所有非关键终结器都在所有关键终结器之前被调用。 对在同一垃圾回收过程中不再运行的对象调用终结器。 例如,FileStream 对象可以运行正常的终结器来刷新现有的缓冲数据,而不会有句柄泄露或被回收的风险。 关键终结器和非关键终结器之间的这种非常薄弱的排序不适用于常规用途。 它主要是为了帮助迁移现有库,通过允许它们在不改变语义的情况下使用 SafeHandle。 此外,关键终结器及其调用的任何内容(如 SafeHandle.ReleaseHandle() 该方法)必须位于受限的执行区域中。 这会对可在终结器调用图中编写哪些代码施加约束。
平台调用操作会自动递增 SafeHandle 封装的句柄的引用计数,并在完成后递减这些句柄。 这可确保句柄不会被意外回收或关闭。
在构造SafeHandle对象时,可以通过在ownsHandle类构造函数中的SafeHandle参数提供值来指定基础句柄的所有权。 这控制 SafeHandle 对象被处置后是否释放句柄。 这对于具有特殊生存期要求的句柄或使用其生存期由其他人控制的句柄非常有用。
派生自 SafeHandle 的类
SafeHandle 是操作系统句柄的抽象包装器类。 从此类派生比较困难。 但可以使用 Microsoft.Win32.SafeHandles 命名空间中可提供以下项的安全句柄的派生类。
- 文件(SafeFileHandle 类)。
- 内存映射文件( SafeMemoryMappedFileHandle 类)。
- 管道( SafePipeHandle 类)。
- 内存视图( SafeMemoryMappedViewHandle 类)。
- 加密构造(SafeNCryptHandle和SafeNCryptKeyHandleSafeNCryptProviderHandleSafeNCryptSecretHandle类)。
- 进程( SafeProcessHandle 类)。
- 注册表项(SafeRegistryHandle 类)。
- 等待句柄(SafeWaitHandle 类)。
实施者说明
若要创建派生自 SafeHandle的类,必须知道如何创建和释放操作系统句柄。 对于不同的句柄类型,此过程有所不同,因为有些使用 CloseHandle 函数,而另一些则使用更具体的函数,例如 UnmapViewOfFile 或 FindClose。 因此,必须为要包装在安全句柄中的每个操作系统句柄类型创建派生类 SafeHandle 。
从 SafeHandle中继承时,必须重写以下成员: IsInvalid 和 ReleaseHandle()。
还应提供一个公共无参数构造函数,该构造函数使用表示无效句柄值的值调用基构造函数,以及一个 Boolean 值,该值指示本机句柄是否由 SafeHandle 该函数拥有,因此应在释放该句柄时 SafeHandle 释放。
构造函数
| 名称 | 说明 |
|---|---|
| SafeHandle(IntPtr, Boolean) |
使用指定的无效句柄值初始化类的新实例 SafeHandle 。 |
字段
| 名称 | 说明 |
|---|---|
| handle |
指定要包装的句柄。 |
属性
| 名称 | 说明 |
|---|---|
| IsClosed |
获取一个值,该值指示句柄是否关闭。 |
| IsInvalid |
在派生类中重写时,获取一个值,该值指示句柄值是否无效。 |
方法
| 名称 | 说明 |
|---|---|
| Close() |
标记释放和释放资源的句柄。 |
| DangerousAddRef(Boolean) |
手动递增实例上的 SafeHandle 引用计数器。 |
| DangerousGetHandle() |
返回字段的值 handle 。 |
| DangerousRelease() |
手动递减实例上的 SafeHandle 引用计数器。 |
| Dispose() |
释放类使用 SafeHandle 的所有资源。 |
| Dispose(Boolean) |
释放类使用 SafeHandle 的非托管资源,该类指定是否执行正常释放操作。 |
| Equals(Object) |
确定指定的对象是否等于当前对象。 (继承自 Object) |
| Finalize() |
释放与句柄关联的所有资源。 |
| GetHashCode() |
用作默认哈希函数。 (继承自 Object) |
| GetType() |
获取当前实例的 Type。 (继承自 Object) |
| MemberwiseClone() |
创建当前 Object的浅表副本。 (继承自 Object) |
| ReleaseHandle() |
在派生类中重写时,执行释放句柄所需的代码。 |
| SetHandle(IntPtr) |
将句柄设置为指定的预先存在的句柄。 |
| SetHandleAsInvalid() |
将句柄标记为不再使用。 |
| ToString() |
返回一个表示当前对象的字符串。 (继承自 Object) |