1 //
2 // System.Runtime.InteropServices.SafeHandle
3 //
4 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
5 //
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:
13 //
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
16 //
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.
24 //
25 // Notes:
26 //     This code is only API complete, but it lacks the runtime support
27 //     for CriticalFinalizerObject and any P/Invoke wrapping that might
28 //     happen.
29 //
30 //     For details, see:
31 //     http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx
32 //
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.
36 //
37 // On implementing SafeHandles:
38 //     http://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx
39 //
40 // Issues:
41 //
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.
45 //
46 
47 //
48 // Copyright (c) Microsoft. All rights reserved.
49 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
50 //
51 // Files:
52 //  - mscorlib/system/runtime/interopservices/safehandle.cs
53 //
54 
55 using System;
56 using System.Runtime.InteropServices;
57 using System.Runtime.ConstrainedExecution;
58 using System.Runtime.CompilerServices;
59 using System.Threading;
60 
61 namespace System.Runtime.InteropServices
62 {
63 	[StructLayout (LayoutKind.Sequential)]
64 	public abstract partial class SafeHandle
65 	{
66 		const int RefCount_Mask = 0x7ffffffc;
67 		const int RefCount_One = 0x4;
68 
69 		enum State {
70 			Closed = 0x00000001,
71 			Disposed = 0x00000002,
72 		}
73 
74 		/*
75 		 * This should only be called for cases when you know for a fact that
76 		 * your handle is invalid and you want to record that information.
77 		 * An example is calling a syscall and getting back ERROR_INVALID_HANDLE.
78 		 * This method will normally leak handles!
79 		 */
80 		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
SetHandleAsInvalid()81 		public void SetHandleAsInvalid ()
82 		{
83 			try {}
84 			finally {
85 				int old_state, new_state;
86 
87 				do {
88 					old_state = _state;
89 					new_state = old_state | (int) State.Closed;
90 				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
91 
92 				GC.SuppressFinalize (this);
93 			}
94 		}
95 
96 		/*
97 		 * Add a reason why this handle should not be relinquished (i.e. have
98 		 * ReleaseHandle called on it). This method has dangerous in the name since
99 		 * it must always be used carefully (e.g. called within a CER) to avoid
100 		 * leakage of the handle. It returns a boolean indicating whether the
101 		 * increment was actually performed to make it easy for program logic to
102 		 * back out in failure cases (i.e. is a call to DangerousRelease needed).
103 		 * It is passed back via a ref parameter rather than as a direct return so
104 		 * that callers need not worry about the atomicity of calling the routine
105 		 * and assigning the return value to a variable (the variable should be
106 		 * explicitly set to false prior to the call). The only failure cases are
107 		 * when the method is interrupted prior to processing by a thread abort or
108 		 * when the handle has already been (or is in the process of being)
109 		 * released.
110 		 */
111 		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
DangerousAddRef(ref bool success)112 		public void DangerousAddRef (ref bool success)
113 		{
114 			try {}
115 			finally {
116 				if (!_fullyInitialized)
117 					throw new InvalidOperationException ();
118 
119 				int old_state, new_state;
120 
121 				do {
122 					old_state = _state;
123 
124 					if ((old_state & (int) State.Closed) != 0)
125 						throw new ObjectDisposedException (null, "Safe handle has been closed");
126 
127 					new_state = old_state + RefCount_One;
128 				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
129 
130 				success = true;
131 			}
132 		}
133 
134 		/*
135 		 * Partner to DangerousAddRef. This should always be successful when used in
136 		 * a correct manner (i.e. matching a successful DangerousAddRef and called
137 		 * from a region such as a CER where a thread abort cannot interrupt
138 		 * processing). In the same way that unbalanced DangerousAddRef calls can
139 		 * cause resource leakage, unbalanced DangerousRelease calls may cause
140 		 * invalid handle states to become visible to other threads. This
141 		 * constitutes a potential security hole (via handle recycling) as well as a
142 		 * correctness problem -- so don't ever expose Dangerous* calls out to
143 		 * untrusted code.
144 		 */
145 		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
DangerousRelease()146 		public void DangerousRelease ()
147 		{
148 			DangerousReleaseInternal (false);
149 		}
150 
InternalDispose()151 		void InternalDispose ()
152 		{
153 			if (!_fullyInitialized)
154 				throw new InvalidOperationException ();
155 
156 			DangerousReleaseInternal (true);
157 			GC.SuppressFinalize (this);
158 		}
159 
InternalFinalize()160 		void InternalFinalize ()
161 		{
162 			if (_fullyInitialized)
163 				DangerousReleaseInternal (true);
164 		}
165 
DangerousReleaseInternal(bool dispose)166 		void DangerousReleaseInternal (bool dispose)
167 		{
168 			try {}
169 			finally {
170 				if (!_fullyInitialized)
171 					throw new InvalidOperationException ();
172 
173 				int old_state, new_state;
174 
175 				/* See AddRef above for the design of the synchronization here. Basically we
176 				 * will try to decrement the current ref count and, if that would take us to
177 				 * zero refs, set the closed state on the handle as well. */
178 				bool perform_release = false;
179 
180 				do {
181 					old_state = _state;
182 
183 					/* If this is a Dispose operation we have additional requirements (to
184 					 * ensure that Dispose happens at most once as the comments in AddRef
185 					 * detail). We must check that the dispose bit is not set in the old
186 					 * state and, in the case of successful state update, leave the disposed
187 					 * bit set. Silently do nothing if Dispose has already been called
188 					 * (because we advertise that as a semantic of Dispose). */
189 					if (dispose && (old_state & (int) State.Disposed) != 0) {
190 						/* we cannot use `return` in a finally block, so we have to ensure
191 						 * that we are not releasing the handle */
192 						perform_release = false;
193 						break;
194 					}
195 
196 					/* We should never see a ref count of zero (that would imply we have
197 					 * unbalanced AddRef and Releases). (We might see a closed state before
198 					 * hitting zero though -- that can happen if SetHandleAsInvalid is
199 					 * used). */
200 					if ((old_state & RefCount_Mask) == 0)
201 						throw new ObjectDisposedException (null, "Safe handle has been closed");
202 
203 					if ((old_state & RefCount_Mask) != RefCount_One)
204 						perform_release = false;
205 					else if ((old_state & (int) State.Closed) != 0)
206 						perform_release = false;
207 					else if (!_ownsHandle)
208 						perform_release = false;
209 					else if (IsInvalid)
210 						perform_release = false;
211 					else
212 						perform_release = true;
213 
214 					/* Attempt the update to the new state, fail and retry if the initial
215 					 * state has been modified in the meantime. Decrement the ref count by
216 					 * substracting SH_RefCountOne from the state then OR in the bits for
217 					 * Dispose (if that's the reason for the Release) and closed (if the
218 					 * initial ref count was 1). */
219 					new_state = (old_state & RefCount_Mask) - RefCount_One;
220 					if ((old_state & RefCount_Mask) == RefCount_One)
221 						new_state |= (int) State.Closed;
222 					if (dispose)
223 						new_state |= (int) State.Disposed;
224 				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
225 
226 				if (perform_release)
227 					ReleaseHandle ();
228 			}
229 		}
230 	}
231 }
232