More corelib cleanup (dotnet/coreclr#26993)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Runtime / InteropServices / SafeHandle.cs
blob053a70f156674ba05999212249146a9df2224981
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Runtime.ConstrainedExecution;
7 using System.Threading;
9 namespace System.Runtime.InteropServices
11 // This implementation does not employ critical execution regions and thus cannot
12 // reliably guarantee handle release in the face of thread aborts.
14 /// <summary>Represents a wrapper class for operating system handles.</summary>
15 public abstract partial class SafeHandle : CriticalFinalizerObject, IDisposable
17 // IMPORTANT:
18 // - Do not add or rearrange fields as the EE depends on this layout,
19 // as well as on the values of the StateBits flags.
20 // - The EE may also perform the same operations using equivalent native
21 // code, so this managed code must not assume it is the only code
22 // manipulating _state.
24 /// <summary>Specifies the handle to be wrapped.</summary>
25 protected IntPtr handle;
26 /// <summary>Combined ref count and closed/disposed flags (so we can atomically modify them).</summary>
27 private volatile int _state;
28 /// <summary>Whether we can release this handle.</summary>
29 private readonly bool _ownsHandle;
30 /// <summary>Whether constructor completed.</summary>
31 private volatile bool _fullyInitialized;
33 /// <summary>Bitmasks for the <see cref="_state"/> field.</summary>
34 /// <remarks>
35 /// The state field ends up looking like this:
36 ///
37 /// 31 2 1 0
38 /// +-----------------------------------------------------------+---+---+
39 /// | Ref count | D | C |
40 /// +-----------------------------------------------------------+---+---+
41 ///
42 /// Where D = 1 means a Dispose has been performed and C = 1 means the
43 /// underlying handle has been (or will be shortly) released.
44 /// </remarks>
45 private static class StateBits
47 public const int Closed = 0b01;
48 public const int Disposed = 0b10;
49 public const int RefCount = unchecked(~0b11); // 2 bits reserved for closed/disposed; ref count gets 30 bits
50 public const int RefCountOne = 1 << 2;
53 /// <summary>Creates a SafeHandle class.</summary>
54 protected SafeHandle(IntPtr invalidHandleValue, bool ownsHandle)
56 handle = invalidHandleValue;
57 _state = StateBits.RefCountOne; // Ref count 1 and not closed or disposed.
58 _ownsHandle = ownsHandle;
60 if (!ownsHandle)
62 GC.SuppressFinalize(this);
65 _fullyInitialized = true;
68 #if !CORERT // CoreRT doesn't correctly support CriticalFinalizerObject
69 ~SafeHandle()
71 if (_fullyInitialized)
73 Dispose(disposing: false);
76 #endif
78 protected void SetHandle(IntPtr handle) => this.handle = handle;
80 public IntPtr DangerousGetHandle() => handle;
82 public bool IsClosed => (_state & StateBits.Closed) == StateBits.Closed;
84 public abstract bool IsInvalid { get; }
86 public void Close() => Dispose();
88 public void Dispose()
90 Dispose(disposing: true);
91 GC.SuppressFinalize(this);
94 protected virtual void Dispose(bool disposing)
96 Debug.Assert(_fullyInitialized);
97 InternalRelease(disposeOrFinalizeOperation: true);
100 public void SetHandleAsInvalid()
102 Debug.Assert(_fullyInitialized);
104 // Attempt to set closed state (low order bit of the _state field).
105 // Might have to attempt these repeatedly, if the operation suffers
106 // interference from an AddRef or Release.
107 int oldState, newState;
110 oldState = _state;
111 newState = oldState | StateBits.Closed;
112 } while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
114 GC.SuppressFinalize(this);
117 protected abstract bool ReleaseHandle();
119 public void DangerousAddRef(ref bool success)
121 // To prevent handle recycling security attacks we must enforce the
122 // following invariant: we cannot successfully AddRef a handle on which
123 // we've committed to the process of releasing.
125 // We ensure this by never AddRef'ing a handle that is marked closed and
126 // never marking a handle as closed while the ref count is non-zero. For
127 // this to be thread safe we must perform inspection/updates of the two
128 // values as a single atomic operation. We achieve this by storing them both
129 // in a single aligned int and modifying the entire state via interlocked
130 // compare exchange operations.
132 // Additionally we have to deal with the problem of the Dispose operation.
133 // We must assume that this operation is directly exposed to untrusted
134 // callers and that malicious callers will try and use what is basically a
135 // Release call to decrement the ref count to zero and free the handle while
136 // it's still in use (the other way a handle recycling attack can be
137 // mounted). We combat this by allowing only one Dispose to operate against
138 // a given safe handle (which balances the creation operation given that
139 // Dispose suppresses finalization). We record the fact that a Dispose has
140 // been requested in the same state field as the ref count and closed state.
142 Debug.Assert(_fullyInitialized);
144 // Might have to perform the following steps multiple times due to
145 // interference from other AddRef's and Release's.
146 int oldState, newState;
149 // First step is to read the current handle state. We use this as a
150 // basis to decide whether an AddRef is legal and, if so, to propose an
151 // update predicated on the initial state (a conditional write).
152 // Check for closed state.
153 oldState = _state;
154 if ((oldState & StateBits.Closed) != 0)
156 throw new ObjectDisposedException(nameof(SafeHandle), SR.ObjectDisposed_SafeHandleClosed);
159 // Not closed, let's propose an update (to the ref count, just add
160 // StateBits.RefCountOne to the state to effectively add 1 to the ref count).
161 // Continue doing this until the update succeeds (because nobody
162 // modifies the state field between the read and write operations) or
163 // the state moves to closed.
164 newState = oldState + StateBits.RefCountOne;
165 } while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
167 // If we got here we managed to update the ref count while the state
168 // remained non closed. So we're done.
169 success = true;
172 public void DangerousRelease() => InternalRelease(disposeOrFinalizeOperation: false);
174 private void InternalRelease(bool disposeOrFinalizeOperation)
176 Debug.Assert(_fullyInitialized || disposeOrFinalizeOperation);
178 // See AddRef above for the design of the synchronization here. Basically we
179 // will try to decrement the current ref count and, if that would take us to
180 // zero refs, set the closed state on the handle as well.
181 bool performRelease = false;
183 // Might have to perform the following steps multiple times due to
184 // interference from other AddRef's and Release's.
185 int oldState, newState;
188 // First step is to read the current handle state. We use this cached
189 // value to predicate any modification we might decide to make to the
190 // state).
191 oldState = _state;
193 // If this is a Dispose operation we have additional requirements (to
194 // ensure that Dispose happens at most once as the comments in AddRef
195 // detail). We must check that the dispose bit is not set in the old
196 // state and, in the case of successful state update, leave the disposed
197 // bit set. Silently do nothing if Dispose has already been called.
198 if (disposeOrFinalizeOperation && ((oldState & StateBits.Disposed) != 0))
200 return;
203 // We should never see a ref count of zero (that would imply we have
204 // unbalanced AddRef and Releases). (We might see a closed state before
205 // hitting zero though -- that can happen if SetHandleAsInvalid is
206 // used).
207 if ((oldState & StateBits.RefCount) == 0)
209 throw new ObjectDisposedException(nameof(SafeHandle), SR.ObjectDisposed_SafeHandleClosed);
212 // If we're proposing a decrement to zero and the handle is not closed
213 // and we own the handle then we need to release the handle upon a
214 // successful state update. If so we need to check whether the handle is
215 // currently invalid by asking the SafeHandle subclass. We must do this before
216 // transitioning the handle to closed, however, since setting the closed
217 // state will cause IsInvalid to always return true.
218 performRelease = ((oldState & (StateBits.RefCount | StateBits.Closed)) == StateBits.RefCountOne) &&
219 _ownsHandle &&
220 !IsInvalid;
222 // Attempt the update to the new state, fail and retry if the initial
223 // state has been modified in the meantime. Decrement the ref count by
224 // substracting StateBits.RefCountOne from the state then OR in the bits for
225 // Dispose (if that's the reason for the Release) and closed (if the
226 // initial ref count was 1).
227 newState = oldState - StateBits.RefCountOne;
228 if ((oldState & StateBits.RefCount) == StateBits.RefCountOne)
230 newState |= StateBits.Closed;
232 if (disposeOrFinalizeOperation)
234 newState |= StateBits.Disposed;
236 } while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
238 // If we get here we successfully decremented the ref count. Additionally we
239 // may have decremented it to zero and set the handle state as closed. In
240 // this case (providng we own the handle) we will call the ReleaseHandle
241 // method on the SafeHandle subclass.
242 if (performRelease)
244 // Save last error from P/Invoke in case the implementation of ReleaseHandle
245 // trashes it (important because this ReleaseHandle could occur implicitly
246 // as part of unmarshaling another P/Invoke).
247 int lastError = Marshal.GetLastWin32Error();
248 ReleaseHandle();
249 Marshal.SetLastWin32Error(lastError);