Oct 212014
 

Introduction

I had a customer who was seeing several invalid handle exceptions in his application. These exceptions were thrown during finalization of  SafeProcessHandle objects. In their code they had a big collection of SafeProcessHandle objects hence too many objects in Finalization queue as well which means too many Invalid Handle exceptions as well.

What’s an Invalid Handle exception?

Invalid handle exceptions basically means the handle being closed has gone bad or has already been closed. In this case the second reason looked more valid to me as the exceptions were happening during finalization. There was a good chance that these handles got closed pretty early.

What’s the reason for these Invalid Handle exceptions?

Lots of Invalid handle exceptions were happening in his code while disposing a SafeProcessHandle object. This was because he’s was not properly serializing the process handles or SafeProcessHandle’s across app domain calls. We enabled MDA – ReleaseHandleFailed. This setting is also reporting something similar.

ReleaseHandleFailed was detected:
Message: A SafeHandle or CriticalHandle of type SafeProcessHandle failed to properly release the handle with value 0x00000000000020F0. This usually indicates that the handle was released incorrectly via another means (such as extracting the handle using DangerousGetHandle and closing it directly or building another SafeHandle around it.)

How to fix these Invalid Handle exceptions?

The only way to fix this invalid handle issue is to manage serialization of SafeProcessHandle objects across app domains. As of now its just a bitwise copy which results in two SafeProcessHandle’s owning a non-ref counted process handle. When either of these SafeProcessHandle objects are destroyed the second instance of SafeProcessHandle is bound to fail as the handle has already been destroyed/closed by the first instance.

Handle’s are indexes into a process handle table which in turn points to the real Kernel Object. This basically means we’ll have to duplicate this handle via DuplicateHandle API and which tells Kernel this object is still alive and not to destroy this Kernel Object. The duplicate handle refers to the same object as the original handle. Therefore, any changes to the object are reflected through both handles. For example, if you duplicate a file handle, the current file position is always the same for both handles.

SafeSerializableProcessHandle to fix Invalid Handle exceptions

Hence SafeSerializableProcessHandle is born. I wrote a dedicated class to manage this serialization. Made this class serializable as well. This class will automatically create a duplicate handle when serialized. For duplicating process handles we’re using the Windows API DuplicateHandle. Here’s how the code looks like…

using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Microsoft.Win32.SafeHandles;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Diagnostics;

namespace NativeHandleWrappers
{
    [System.Security.SecurityCritical]
    [Serializable()]
    public class SafeSerializableProcessHandle : SafeHandleZeroOrMinusOneIsInvalid, ISerializable
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, EntryPoint = "CloseHandle", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandleImport(IntPtr hObject);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, EntryPoint = "DuplicateHandle", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool DuplicateHandleImport(IntPtr hSourceProcessHandle,
                                           IntPtr hSourceHandle,
                                           IntPtr hTargetProcessHandle,
                                           out SafeSerializableProcessHandle lpTargetHandle,
                                           uint dwDesiredAccess,
                                           [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
                                           uint dwOptions);

        [Flags]
        public enum DuplicateOptions : uint
        {
            DUPLICATE_CLOSE_SOURCE = (0x00000001),// Closes the source handle. This occurs regardless of any error status returned.
            DUPLICATE_SAME_ACCESS = (0x00000002), //Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
        }

        // Constructors //
        private SafeSerializableProcessHandle()
            : base(true)
        {
            handle = IntPtr.Zero;
        }

        // Constructor that is called automatically during deserialization.
        // Reconstructs the object from the information in SerializationInfo info
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        private SafeSerializableProcessHandle(SerializationInfo Info, StreamingContext Context)
            : base(true)
        {
            handle = ((IntPtr)Info.GetInt64("handle"));
        }

        public SafeSerializableProcessHandle(IntPtr handle)
            : base(true)
        {
            SetHandle(handle);
        }
        // End constructors //

        // 0 is an Invalid Handle
        internal static SafeSerializableProcessHandle InvalidHandle
        {
            get { return new SafeSerializableProcessHandle(IntPtr.Zero); }
        }

        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        override protected bool ReleaseHandle()
        {
            bool Status = true;

            if (!SuppressRelease)
            {
                Status = CloseHandleImport(handle);
                if (!Status)
                {
                    DebugOutLastError();
                }
            }

            return Status;
        }

        void DebugOutLastError()
        {
            System.ComponentModel.Win32Exception we = new System.ComponentModel.Win32Exception();
            Debug.WriteLine("\n------------------------------------------\nLast error: " + we.Message + "\n------------------------------------------\n");
        }

        private bool _suppressRelease = false;
        protected bool SuppressRelease
        {
            get
            {
                return _suppressRelease;
            }

            set
            {
                _suppressRelease = value;
            }
        }

        // Serializes the object.
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            SafeSerializableProcessHandle hDupHandle = DuplicateHandlePrivate(handle);
            info.AddValue("handle", hDupHandle.handle.ToInt64());

            // Suppress close handle as the handle will be closed by the instance that's deserializing
            hDupHandle.SuppressRelease = true;
            hDupHandle.Close();
        }

        private SafeSerializableProcessHandle DuplicateHandlePrivate(IntPtr Handle)
        {
            SafeSerializableProcessHandle hDupHandle;
            bool Status = DuplicateHandleImport(Process.GetCurrentProcess().Handle,
                                                Handle,
                                                Process.GetCurrentProcess().Handle,
                                                out hDupHandle,
                                                0,
                                                true,
                                                (uint)DuplicateOptions.DUPLICATE_SAME_ACCESS);
            if (hDupHandle == null || !Status)
            {
                Debug.Assert(false);
                DebugOutLastError();
            }

            return hDupHandle;
        }
    }
}

//Function to test SafeSerializableProcessHandle class. This function also demonstrates usage of SafeSerializableProcessHandle
void Serialize()
{
    FileStream fs = new FileStream("DataFile.dat", FileMode.Create|FileMode.CreateNew);

    try
    {
        // Construct a BinaryFormatter and use it to serialize the data to the stream.
        BinaryFormatter formatter = new BinaryFormatter();

        // Serialize
        Process [] Proc = Process.GetProcessesByName("Explorer");
        SafeSerializableProcessHandle ssph = OpenProcess(ProcessAccessFlags.DupHandle | ProcessAccessFlags.Terminate, true, Proc[0].Id);
        formatter.Serialize(fs, ssph);

        //Deserialize.
        fs.Position = 0;
        SafeSerializableProcessHandle newssph = (SafeSerializableProcessHandle)formatter.Deserialize(fs);

        // Close both, this should work
        ssph.Close();
        newssph.Close();
    }
    catch (SerializationException e)
    {
        Console.WriteLine("Failed to serialize. Reason: " + e.Message);
        throw;
    }
    finally
    {
        fs.Close();
    }
}

Usage of SafeSerializableProcessHandle

Replace instances of SafeProcessHandle, which will be cross domained or serialized, with SafeSerializableProcessHandle. No other change will be needed. The effect is magical, no invalid handle exceptions any more. 🙂

Caveats/Disclaimer

Please note this class is not extensively tested. I did some basic tests and so far looks like this is working for our customer as well. So I guess this is pretty solid. If you run into issues you can let me know via a comment to this post but this post is given as is without any sort of support. You’re going to use this at your own risk.