SafeHandle DangerousGetHandle Called
ID |
csharp.safehandle_dangerousgethandle_called |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
handle, interop, reliability, resource-leak |
Description
Reports a call to DangerousGetHandle() on a handle wrapper. The method returns the raw IntPtr
behind the wrapper, and with it every guarantee the wrapper was introduced to provide.
Rationale
A handle wrapper exists to answer two questions the raw IntPtr cannot: is this handle still valid,
and who releases it. It keeps the handle alive while it is in use, releases it exactly once, and does
so even if the thread is aborted between the acquire and the release.
DangerousGetHandle() returns a value the runtime no longer tracks. Nothing keeps the wrapper
reachable while the raw pointer is in flight, so its finalizer may release the underlying handle
before — or during — the native call that receives the pointer. The operating system is then free to
reissue the same numeric handle to the next allocation, and the pending call operates on a completely
unrelated file, socket or registry key. That is the handle-recycling problem the wrapper types were
designed to eliminate, and the name of the method is the framework saying so.
Using the returned pointer correctly means bracketing every use with a matched
DangerousAddRef/DangerousRelease pair, keeping the pair balanced on every exception path, and
never letting the pointer outlive the block. That is a lot of invariant to maintain by hand for a
value that is only needed because an interop signature was declared with IntPtr instead of the
handle type.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class Native
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FlushFileBuffers(SafeFileHandle handle);
public bool FlushRaw(SafeFileHandle file)
{
IntPtr raw = file.DangerousGetHandle(); // FLAW — may already be released and reissued
return raw != IntPtr.Zero;
}
public bool Flush(SafeFileHandle file)
{
return FlushFileBuffers(file); // OK — the marshaller keeps the handle alive
}
}
Remediation
Prefer letting the interop layer handle the lifetime.
-
Declare the
DllImportparameter as the handle type (SafeFileHandle,SafeWaitHandle, or a project-specificSafeHandlesubclass) instead ofIntPtr. The marshaller then keeps the wrapper alive across the call and passes the raw value itself, which removes the need for the call entirely. -
Return a handle subclass from the native function rather than an
IntPtr, so the wrapper owns the handle from the moment it is created. -
When the raw pointer really is unavoidable — a callback signature outside your control, for instance — bracket it with
DangerousAddRef(ref success)and aDangerousRelease()in afinally, and keep the pointer inside that block.