Merge pull request #3056 from BrzVlad/fix-multiple-binprot
[mono.git] / mcs / class / corlib / System.Runtime.InteropServices / SafeHandle.cs
index c706ceed6184073e2a374ed306b1eaf70d0d5c16..b5b59a1ae81ffb3f37822fd47ba5619f08d7469a 100644 (file)
@@ -61,50 +61,8 @@ using System.Threading;
 namespace System.Runtime.InteropServices
 {
        [StructLayout (LayoutKind.Sequential)]
-       public abstract class SafeHandle : CriticalFinalizerObject, IDisposable
+       public abstract partial class SafeHandle
        {
-               /* Warning: the offset of handle is mapped inside the runtime
-                * if you move this, you must updated the runtime definition of
-                * MonoSafeHandle */
-               protected IntPtr handle;
-
-               /*
-                * To prevent handle recycling security attacks we must enforce the
-                * following invariant: we cannot successfully AddRef a handle on which
-                * we've committed to the process of releasing.
-                *
-                * We ensure this by never AddRef'ing a handle that is marked closed and
-                * never marking a handle as closed while the ref count is non-zero. For
-                * this to be thread safe we must perform inspection/updates of the two
-                * values as a single atomic operation. We achieve this by storing them both
-                * in a single aligned DWORD and modifying the entire state via interlocked
-                * compare exchange operations.
-                *
-                * Additionally we have to deal with the problem of the Dispose operation.
-                * We must assume that this operation is directly exposed to untrusted
-                * callers and that malicious callers will try and use what is basically a
-                * Release call to decrement the ref count to zero and free the handle while
-                * it's still in use (the other way a handle recycling attack can be
-                * mounted). We combat this by allowing only one Dispose to operate against
-                * a given safe handle (which balances the creation operation given that
-                * Dispose suppresses finalization). We record the fact that a Dispose has
-                * been requested in the same state field as the ref count and closed state.
-                *
-                * So the state field ends up looking like this:
-                *
-                *  31                                                        2  1   0
-                * +-----------------------------------------------------------+---+---+
-                * |                           Ref count                       | D | C |
-                * +-----------------------------------------------------------+---+---+
-                *
-                * Where D = 1 means a Dispose has been performed and C = 1 means the
-                * underlying handle has (or will be shortly) released.
-                */
-               int state;
-
-               bool owns_handle;
-               bool fully_initialized;
-
                const int RefCount_Mask = 0x7ffffffc;
                const int RefCount_One = 0x4;
 
@@ -113,49 +71,6 @@ namespace System.Runtime.InteropServices
                        Disposed = 0x00000002,
                }
 
-#if NET_2_1
-               protected SafeHandle ()
-               {
-                       throw new NotImplementedException ();
-               }
-#endif
-
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
-               protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle)
-               {
-                       handle = invalidHandleValue;
-                       state = RefCount_One;
-                       owns_handle = ownsHandle;
-
-                       if (!owns_handle)
-                               GC.SuppressFinalize (this);
-
-                       fully_initialized = true;
-               }
-
-               ~SafeHandle ()
-               {
-                       Dispose (false);
-               }
-
-               public bool IsClosed {
-                       [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-                       get {
-                               return (state & (int) State.Closed) != 0;
-                       }
-               }
-
-               public abstract bool IsInvalid {
-                       [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-                       get;
-               }
-
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               protected void SetHandle (IntPtr handle)
-               {
-                       this.handle = handle;
-               }
-
                /*
                 * This should only be called for cases when you know for a fact that
                 * your handle is invalid and you want to record that information.
@@ -165,36 +80,17 @@ namespace System.Runtime.InteropServices
                [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                public void SetHandleAsInvalid ()
                {
-                       int old_state, new_state;
-
-                       do {
-                               old_state = state;
-                               new_state = old_state | (int) State.Closed;
-                       } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
+                       try {}
+                       finally {
+                               int old_state, new_state;
 
-                       GC.SuppressFinalize (this);
-               }
+                               do {
+                                       old_state = _state;
+                                       new_state = old_state | (int) State.Closed;
+                               } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
 
-               /*
-                * This method is necessary for getting an IntPtr out of a SafeHandle.
-                * Used to tell whether a call to create the handle succeeded by comparing
-                * the handle against a known invalid value, and for backwards
-                * compatibility to support the handle properties returning IntPtrs on
-                * many of our Framework classes.
-                * Note that this method is dangerous for two reasons:
-                *  1) If the handle has been marked invalid with SetHandleasInvalid,
-                *     DangerousGetHandle will still return the original handle value.
-                *  2) The handle returned may be recycled at any point. At best this means
-                *     the handle might stop working suddenly. At worst, if the handle or
-                *     the resource the handle represents is exposed to untrusted code in
-                *     any way, this can lead to a handle recycling security attack (i.e. an
-                *     untrusted caller can query data on the handle you've just returned
-                *     and get back information for an entirely unrelated resource).
-                */
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public IntPtr DangerousGetHandle ()
-               {
-                       return handle;
+                               GC.SuppressFinalize (this);
+                       }
                }
 
                /*
@@ -215,21 +111,24 @@ namespace System.Runtime.InteropServices
                [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
                public void DangerousAddRef (ref bool success)
                {
-                       if (!fully_initialized)
-                               throw new InvalidOperationException ();
+                       try {}
+                       finally {
+                               if (!_fullyInitialized)
+                                       throw new InvalidOperationException ();
 
-                       int old_state, new_state;
+                               int old_state, new_state;
 
-                       do {
-                               old_state = state;
+                               do {
+                                       old_state = _state;
 
-                               if ((old_state & (int) State.Closed) != 0)
-                                       throw new ObjectDisposedException ("handle");
+                                       if ((old_state & (int) State.Closed) != 0)
+                                               throw new ObjectDisposedException ("handle");
 
-                               new_state = old_state + RefCount_One;
-                       } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
+                                       new_state = old_state + RefCount_One;
+                               } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
 
-                       success = true;
+                               success = true;
+                       }
                }
 
                /*
@@ -249,103 +148,84 @@ namespace System.Runtime.InteropServices
                        DangerousReleaseInternal (false);
                }
 
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public void Close ()
+               void InternalDispose ()
                {
-                       Dispose (true);
-               }
+                       if (!_fullyInitialized)
+                               throw new InvalidOperationException ();
 
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public void Dispose ()
-               {
-                       Dispose (true);
+                       DangerousReleaseInternal (true);
                        GC.SuppressFinalize (this);
                }
 
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               protected virtual void Dispose (bool disposing)
-               {
-                       if (disposing) {
-                               if (!fully_initialized)
-                                       throw new InvalidOperationException ();
-                               DisposeInternal ();
-                       } else {
-                               if (fully_initialized)
-                                       DisposeInternal ();
-                       }
-               }
-
-               void DisposeInternal ()
+               void InternalFinalize ()
                {
-                       DangerousReleaseInternal (true);
-                       GC.SuppressFinalize (this);
+                       if (_fullyInitialized)
+                               DangerousReleaseInternal (true);
                }
 
                void DangerousReleaseInternal (bool dispose)
                {
-                       if (!fully_initialized)
-                               throw new InvalidOperationException ();
-
-                       int old_state, new_state;
-
-                       /* See AddRef above for the design of the synchronization here. Basically we
-                        * will try to decrement the current ref count and, if that would take us to
-                        * zero refs, set the closed state on the handle as well. */
-                       bool perform_release = false;
-
-                       do {
-                               old_state = state;
-
-                               /* If this is a Dispose operation we have additional requirements (to
-                                * ensure that Dispose happens at most once as the comments in AddRef
-                                * detail). We must check that the dispose bit is not set in the old
-                                * state and, in the case of successful state update, leave the disposed
-                                * bit set. Silently do nothing if Dispose has already been called
-                                * (because we advertise that as a semantic of Dispose). */
-                               if (dispose && (old_state & (int) State.Disposed) != 0)
-                                       return;
-
-                               /* We should never see a ref count of zero (that would imply we have
-                                * unbalanced AddRef and Releases). (We might see a closed state before
-                                * hitting zero though -- that can happen if SetHandleAsInvalid is
-                                * used). */
-                               if ((old_state & RefCount_Mask) == 0)
-                                       throw new ObjectDisposedException ("handle");
-
-                               perform_release =
-                                       (old_state & RefCount_Mask) == RefCount_One
-                                        && (old_state & (int) State.Closed) == 0
-                                        && owns_handle;
-
-                               if (perform_release && IsInvalid)
-                                       perform_release = false;
-
-                               /* Attempt the update to the new state, fail and retry if the initial
-                                * state has been modified in the meantime. Decrement the ref count by
-                                * substracting SH_RefCountOne from the state then OR in the bits for
-                                * Dispose (if that's the reason for the Release) and closed (if the
-                                * initial ref count was 1). */
-                               new_state =
-                                       (old_state - RefCount_One)
-                                        | ((old_state & RefCount_Mask) == RefCount_One ? (int) State.Closed : 0)
-                                        | (dispose ? (int) State.Disposed : 0);
-                       } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
+                       try {}
+                       finally {
+                               if (!_fullyInitialized)
+                                       throw new InvalidOperationException ();
 
-                       if (perform_release)
-                               ReleaseHandle ();
+                               int old_state, new_state;
+
+                               /* See AddRef above for the design of the synchronization here. Basically we
+                                * will try to decrement the current ref count and, if that would take us to
+                                * zero refs, set the closed state on the handle as well. */
+                               bool perform_release = false;
+
+                               do {
+                                       old_state = _state;
+
+                                       /* If this is a Dispose operation we have additional requirements (to
+                                        * ensure that Dispose happens at most once as the comments in AddRef
+                                        * detail). We must check that the dispose bit is not set in the old
+                                        * state and, in the case of successful state update, leave the disposed
+                                        * bit set. Silently do nothing if Dispose has already been called
+                                        * (because we advertise that as a semantic of Dispose). */
+                                       if (dispose && (old_state & (int) State.Disposed) != 0) {
+                                               /* we cannot use `return` in a finally block, so we have to ensure
+                                                * that we are not releasing the handle */
+                                               perform_release = false;
+                                               break;
+                                       }
+
+                                       /* We should never see a ref count of zero (that would imply we have
+                                        * unbalanced AddRef and Releases). (We might see a closed state before
+                                        * hitting zero though -- that can happen if SetHandleAsInvalid is
+                                        * used). */
+                                       if ((old_state & RefCount_Mask) == 0)
+                                               throw new ObjectDisposedException ("handle");
+
+                                       if ((old_state & RefCount_Mask) != RefCount_One)
+                                               perform_release = false;
+                                       else if ((old_state & (int) State.Closed) != 0)
+                                               perform_release = false;
+                                       else if (!_ownsHandle)
+                                               perform_release = false;
+                                       else if (IsInvalid)
+                                               perform_release = false;
+                                       else
+                                               perform_release = true;
+
+                                       /* Attempt the update to the new state, fail and retry if the initial
+                                        * state has been modified in the meantime. Decrement the ref count by
+                                        * substracting SH_RefCountOne from the state then OR in the bits for
+                                        * Dispose (if that's the reason for the Release) and closed (if the
+                                        * initial ref count was 1). */
+                                       new_state = (old_state & RefCount_Mask) - RefCount_One;
+                                       if ((old_state & RefCount_Mask) == RefCount_One)
+                                               new_state |= (int) State.Closed;
+                                       if (dispose)
+                                               new_state |= (int) State.Disposed;
+                               } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
+
+                               if (perform_release)
+                                       ReleaseHandle ();
+                       }
                }
-
-               /*
-                * Implement this abstract method in your derived class to specify how to
-                * free the handle. Be careful not write any code that's subject to faults
-                * in this method (the runtime will prepare the infrastructure for you so
-                * that no jit allocations etc. will occur, but don't allocate memory unless
-                * you can deal with the failure and still free the handle).
-                * The boolean returned should be true for success and false if the runtime
-                * should fire a SafeHandleCriticalFailure MDA (CustomerDebugProbe) if that
-                * MDA is enabled.
-                */
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               protected abstract bool ReleaseHandle ();
        }
 }