2 // System.Runtime.InteropServices.SafeHandle
4 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the
8 // "Software"), to deal in the Software without restriction, including
9 // without limitation the rights to use, copy, modify, merge, publish,
10 // distribute, sublicense, and/or sell copies of the Software, and to
11 // permit persons to whom the Software is furnished to do so, subject to
12 // the following conditions:
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 // This code is only API complete, but it lacks the runtime support
27 // for CriticalFinalizerObject and any P/Invoke wrapping that might
31 // http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx
33 // CER-like behavior is implemented for Close and DangerousAddRef
34 // via the try/finally uninterruptible pattern in case of async
35 // exceptions like ThreadAbortException.
37 // On implementing SafeHandles:
38 // http://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx
42 // TODO: Although DangerousAddRef has been implemented, I need to
43 // find out whether the runtime performs the P/Invoke if the
44 // handle has been disposed already.
48 // Copyright (c) Microsoft. All rights reserved.
49 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
52 // - mscorlib/system/runtime/interopservices/safehandle.cs
56 using System.Runtime.InteropServices;
57 using System.Runtime.ConstrainedExecution;
58 using System.Runtime.CompilerServices;
59 using System.Threading;
61 namespace System.Runtime.InteropServices
63 [StructLayout (LayoutKind.Sequential)]
64 public abstract class SafeHandle : CriticalFinalizerObject, IDisposable
66 /* Warning: the offset of handle is mapped inside the runtime
67 * if you move this, you must updated the runtime definition of
69 protected IntPtr handle;
72 * To prevent handle recycling security attacks we must enforce the
73 * following invariant: we cannot successfully AddRef a handle on which
74 * we've committed to the process of releasing.
76 * We ensure this by never AddRef'ing a handle that is marked closed and
77 * never marking a handle as closed while the ref count is non-zero. For
78 * this to be thread safe we must perform inspection/updates of the two
79 * values as a single atomic operation. We achieve this by storing them both
80 * in a single aligned DWORD and modifying the entire state via interlocked
81 * compare exchange operations.
83 * Additionally we have to deal with the problem of the Dispose operation.
84 * We must assume that this operation is directly exposed to untrusted
85 * callers and that malicious callers will try and use what is basically a
86 * Release call to decrement the ref count to zero and free the handle while
87 * it's still in use (the other way a handle recycling attack can be
88 * mounted). We combat this by allowing only one Dispose to operate against
89 * a given safe handle (which balances the creation operation given that
90 * Dispose suppresses finalization). We record the fact that a Dispose has
91 * been requested in the same state field as the ref count and closed state.
93 * So the state field ends up looking like this:
96 * +-----------------------------------------------------------+---+---+
97 * | Ref count | D | C |
98 * +-----------------------------------------------------------+---+---+
100 * Where D = 1 means a Dispose has been performed and C = 1 means the
101 * underlying handle has (or will be shortly) released.
106 bool fully_initialized;
108 const int RefCount_Mask = 0x7ffffffc;
109 const int RefCount_One = 0x4;
113 Disposed = 0x00000002,
117 protected SafeHandle ()
119 throw new NotImplementedException ();
123 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
124 protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle)
126 handle = invalidHandleValue;
127 state = RefCount_One;
128 owns_handle = ownsHandle;
131 GC.SuppressFinalize (this);
133 fully_initialized = true;
141 public bool IsClosed {
142 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
144 return (state & (int) State.Closed) != 0;
148 public abstract bool IsInvalid {
149 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
153 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
154 protected void SetHandle (IntPtr handle)
156 this.handle = handle;
160 * This should only be called for cases when you know for a fact that
161 * your handle is invalid and you want to record that information.
162 * An example is calling a syscall and getting back ERROR_INVALID_HANDLE.
163 * This method will normally leak handles!
165 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
166 public void SetHandleAsInvalid ()
168 int old_state, new_state;
172 new_state = old_state | (int) State.Closed;
173 } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
175 GC.SuppressFinalize (this);
179 * This method is necessary for getting an IntPtr out of a SafeHandle.
180 * Used to tell whether a call to create the handle succeeded by comparing
181 * the handle against a known invalid value, and for backwards
182 * compatibility to support the handle properties returning IntPtrs on
183 * many of our Framework classes.
184 * Note that this method is dangerous for two reasons:
185 * 1) If the handle has been marked invalid with SetHandleasInvalid,
186 * DangerousGetHandle will still return the original handle value.
187 * 2) The handle returned may be recycled at any point. At best this means
188 * the handle might stop working suddenly. At worst, if the handle or
189 * the resource the handle represents is exposed to untrusted code in
190 * any way, this can lead to a handle recycling security attack (i.e. an
191 * untrusted caller can query data on the handle you've just returned
192 * and get back information for an entirely unrelated resource).
194 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
195 public IntPtr DangerousGetHandle ()
201 * Add a reason why this handle should not be relinquished (i.e. have
202 * ReleaseHandle called on it). This method has dangerous in the name since
203 * it must always be used carefully (e.g. called within a CER) to avoid
204 * leakage of the handle. It returns a boolean indicating whether the
205 * increment was actually performed to make it easy for program logic to
206 * back out in failure cases (i.e. is a call to DangerousRelease needed).
207 * It is passed back via a ref parameter rather than as a direct return so
208 * that callers need not worry about the atomicity of calling the routine
209 * and assigning the return value to a variable (the variable should be
210 * explicitly set to false prior to the call). The only failure cases are
211 * when the method is interrupted prior to processing by a thread abort or
212 * when the handle has already been (or is in the process of being)
215 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
216 public void DangerousAddRef (ref bool success)
218 if (!fully_initialized)
219 throw new InvalidOperationException ();
221 int old_state, new_state;
226 if ((old_state & (int) State.Closed) != 0)
227 throw new ObjectDisposedException ("handle");
229 new_state = old_state + RefCount_One;
230 } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
236 * Partner to DangerousAddRef. This should always be successful when used in
237 * a correct manner (i.e. matching a successful DangerousAddRef and called
238 * from a region such as a CER where a thread abort cannot interrupt
239 * processing). In the same way that unbalanced DangerousAddRef calls can
240 * cause resource leakage, unbalanced DangerousRelease calls may cause
241 * invalid handle states to become visible to other threads. This
242 * constitutes a potential security hole (via handle recycling) as well as a
243 * correctness problem -- so don't ever expose Dangerous* calls out to
246 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
247 public void DangerousRelease ()
249 DangerousReleaseInternal (false);
252 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
258 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
259 public void Dispose ()
262 GC.SuppressFinalize (this);
265 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
266 protected virtual void Dispose (bool disposing)
269 if (!fully_initialized)
270 throw new InvalidOperationException ();
273 if (fully_initialized)
278 void DisposeInternal ()
280 DangerousReleaseInternal (true);
281 GC.SuppressFinalize (this);
284 void DangerousReleaseInternal (bool dispose)
286 if (!fully_initialized)
287 throw new InvalidOperationException ();
289 int old_state, new_state;
291 /* See AddRef above for the design of the synchronization here. Basically we
292 * will try to decrement the current ref count and, if that would take us to
293 * zero refs, set the closed state on the handle as well. */
294 bool perform_release = false;
299 /* If this is a Dispose operation we have additional requirements (to
300 * ensure that Dispose happens at most once as the comments in AddRef
301 * detail). We must check that the dispose bit is not set in the old
302 * state and, in the case of successful state update, leave the disposed
303 * bit set. Silently do nothing if Dispose has already been called
304 * (because we advertise that as a semantic of Dispose). */
305 if (dispose && (old_state & (int) State.Disposed) != 0)
308 /* We should never see a ref count of zero (that would imply we have
309 * unbalanced AddRef and Releases). (We might see a closed state before
310 * hitting zero though -- that can happen if SetHandleAsInvalid is
312 if ((old_state & RefCount_Mask) == 0)
313 throw new ObjectDisposedException ("handle");
316 (old_state & RefCount_Mask) == RefCount_One
317 && (old_state & (int) State.Closed) == 0
320 if (perform_release && IsInvalid)
321 perform_release = false;
323 /* Attempt the update to the new state, fail and retry if the initial
324 * state has been modified in the meantime. Decrement the ref count by
325 * substracting SH_RefCountOne from the state then OR in the bits for
326 * Dispose (if that's the reason for the Release) and closed (if the
327 * initial ref count was 1). */
329 (old_state - RefCount_One)
330 | ((old_state & RefCount_Mask) == RefCount_One ? (int) State.Closed : 0)
331 | (dispose ? (int) State.Disposed : 0);
332 } while (Interlocked.CompareExchange (ref state, new_state, old_state) != old_state);
339 * Implement this abstract method in your derived class to specify how to
340 * free the handle. Be careful not write any code that's subject to faults
341 * in this method (the runtime will prepare the infrastructure for you so
342 * that no jit allocations etc. will occur, but don't allocate memory unless
343 * you can deal with the failure and still free the handle).
344 * The boolean returned should be true for success and false if the runtime
345 * should fire a SafeHandleCriticalFailure MDA (CustomerDebugProbe) if that
348 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
349 protected abstract bool ReleaseHandle ();