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 DllImport parameter as the handle type (SafeFileHandle, SafeWaitHandle, or a project-specific SafeHandle subclass) instead of IntPtr. 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 a DangerousRelease() in a finally, and keep the pointer inside that block.