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.
4
5//
6
7/*============================================================
8**
9** Class: SafeHandle
10**
11**
12** Purpose: The unmanaged implementation of the SafeHandle
13** class
14**
15===========================================================*/
16
17#include "common.h"
18#include "vars.hpp"
19#include "object.h"
20#include "excep.h"
21#include "frames.h"
22#include "eecontract.h"
23#include "mdaassistants.h"
24#include "typestring.h"
25
26WORD SafeHandle::s_IsInvalidHandleMethodSlot = MethodTable::NO_SLOT;
27WORD SafeHandle::s_ReleaseHandleMethodSlot = MethodTable::NO_SLOT;
28
29void SafeHandle::Init()
30{
31 CONTRACTL {
32 THROWS;
33 GC_TRIGGERS;
34 MODE_ANY;
35 } CONTRACTL_END;
36
37 // For reliability purposes, we need to eliminate all possible failure
38 // points before making a call to a CER method. IsInvalidHandle, and
39 // ReleaseHandle methods are critical calls that are already prepared (code:
40 // PrepareCriticalFinalizerObject). As a performance optimization, we are
41 // calling these methods through a fast macro that assumes the method slot
42 // has been already cached. Since figuring out the method slot for these 2
43 // methods involves calling .GetMethod which can fail, we are doing this
44 // eagerly here, Otherwise we will have to do it at the time of the call,
45 // and this could be at risk if .GetMethod failed.
46 MethodDesc* pMD = MscorlibBinder::GetMethod(METHOD__SAFE_HANDLE__GET_IS_INVALID);
47 s_IsInvalidHandleMethodSlot = pMD->GetSlot();
48
49 pMD = MscorlibBinder::GetMethod(METHOD__SAFE_HANDLE__RELEASE_HANDLE);
50 s_ReleaseHandleMethodSlot = pMD->GetSlot();
51}
52
53void SafeHandle::AddRef()
54{
55 CONTRACTL {
56 THROWS;
57 GC_TRIGGERS;
58 MODE_COOPERATIVE;
59 INSTANCE_CHECK;
60 } CONTRACTL_END;
61
62 // Cannot use "this" after Release, which toggles the GC mode.
63 SAFEHANDLEREF sh(this);
64
65 _ASSERTE(sh->IsFullyInitialized());
66
67 // To prevent handle recycling security attacks we must enforce the
68 // following invariant: we cannot successfully AddRef a handle on which
69 // we've committed to the process of releasing.
70
71 // We ensure this by never AddRef'ing a handle that is marked closed and
72 // never marking a handle as closed while the ref count is non-zero. For
73 // this to be thread safe we must perform inspection/updates of the two
74 // values as a single atomic operation. We achieve this by storing them both
75 // in a single aligned DWORD and modifying the entire state via interlocked
76 // compare exchange operations.
77
78 // Additionally we have to deal with the problem of the Dispose operation.
79 // We must assume that this operation is directly exposed to untrusted
80 // callers and that malicious callers will try and use what is basically a
81 // Release call to decrement the ref count to zero and free the handle while
82 // it's still in use (the other way a handle recycling attack can be
83 // mounted). We combat this by allowing only one Dispose to operate against
84 // a given safe handle (which balances the creation operation given that
85 // Dispose suppresses finalization). We record the fact that a Dispose has
86 // been requested in the same state field as the ref count and closed state.
87
88 // So the state field ends up looking like this:
89 //
90 // 31 2 1 0
91 // +-----------------------------------------------------------+---+---+
92 // | Ref count | D | C |
93 // +-----------------------------------------------------------+---+---+
94 //
95 // Where D = 1 means a Dispose has been performed and C = 1 means the
96 // underlying handle has (or will be shortly) released.
97
98 // Might have to perform the following steps multiple times due to
99 // interference from other AddRef's and Release's.
100 INT32 oldState, newState;
101 do {
102
103 // First step is to read the current handle state. We use this as a
104 // basis to decide whether an AddRef is legal and, if so, to propose an
105 // update predicated on the initial state (a conditional write).
106 oldState = sh->m_state;
107
108 // Check for closed state.
109 if (oldState & SH_State_Closed)
110 COMPlusThrow(kObjectDisposedException, IDS_EE_SAFEHANDLECLOSED);
111
112 // Not closed, let's propose an update (to the ref count, just add
113 // SH_RefCountOne to the state to effectively add 1 to the ref count).
114 // Continue doing this until the update succeeds (because nobody
115 // modifies the state field between the read and write operations) or
116 // the state moves to closed.
117 newState = oldState + SH_RefCountOne;
118
119 } while (InterlockedCompareExchange((LONG*)&sh->m_state, newState, oldState) != oldState);
120
121 // If we got here we managed to update the ref count while the state
122 // remained non closed. So we're done.
123}
124
125void SafeHandle::Release(bool fDispose)
126{
127 CONTRACTL {
128 THROWS;
129 GC_TRIGGERS;
130 MODE_COOPERATIVE;
131 INSTANCE_CHECK;
132 } CONTRACTL_END;
133
134 // Cannot use "this" after RunReleaseMethod, which toggles the GC mode.
135 SAFEHANDLEREF sh(this);
136
137 _ASSERTE(sh->IsFullyInitialized());
138
139 // See AddRef above for the design of the synchronization here. Basically we
140 // will try to decrement the current ref count and, if that would take us to
141 // zero refs, set the closed state on the handle as well.
142 bool fPerformRelease = false;
143
144 // Might have to perform the following steps multiple times due to
145 // interference from other AddRef's and Release's.
146 INT32 oldState, newState;
147 do {
148
149 // First step is to read the current handle state. We use this cached
150 // value to predicate any modification we might decide to make to the
151 // state).
152 oldState = sh->m_state;
153
154 // If this is a Dispose operation we have additional requirements (to
155 // ensure that Dispose happens at most once as the comments in AddRef
156 // detail). We must check that the dispose bit is not set in the old
157 // state and, in the case of successful state update, leave the disposed
158 // bit set. Silently do nothing if Dispose has already been called
159 // (because we advertise that as a semantic of Dispose).
160 if (fDispose && (oldState & SH_State_Disposed))
161 return;
162
163 // We should never see a ref count of zero (that would imply we have
164 // unbalanced AddRef and Releases). (We might see a closed state before
165 // hitting zero though -- that can happen if SetHandleAsInvalid is
166 // used).
167 if ((oldState & SH_State_RefCount) == 0)
168 COMPlusThrow(kObjectDisposedException, IDS_EE_SAFEHANDLECLOSED);
169
170 // If we're proposing a decrement to zero and the handle is not closed
171 // and we own the handle then we need to release the handle upon a
172 // successful state update.
173 fPerformRelease = ((oldState & (SH_State_RefCount | SH_State_Closed)) == SH_RefCountOne) && m_ownsHandle;
174
175 // If so we need to check whether the handle is currently invalid by
176 // asking the SafeHandle subclass. We must do this *before*
177 // transitioning the handle to closed, however, since setting the closed
178 // state will cause IsInvalid to always return true.
179 if (fPerformRelease)
180 {
181 GCPROTECT_BEGIN(sh);
182
183 CLR_BOOL fIsInvalid = FALSE;
184
185 DECLARE_ARGHOLDER_ARRAY(args, 1);
186 args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(sh);
187
188 PREPARE_SIMPLE_VIRTUAL_CALLSITE_USING_SLOT(s_IsInvalidHandleMethodSlot, sh);
189
190 CRITICAL_CALLSITE;
191 CALL_MANAGED_METHOD(fIsInvalid, CLR_BOOL, args);
192
193 if (fIsInvalid)
194 {
195 fPerformRelease = false;
196 }
197
198 GCPROTECT_END();
199 }
200
201 // Attempt the update to the new state, fail and retry if the initial
202 // state has been modified in the meantime. Decrement the ref count by
203 // substracting SH_RefCountOne from the state then OR in the bits for
204 // Dispose (if that's the reason for the Release) and closed (if the
205 // initial ref count was 1).
206 newState = (oldState - SH_RefCountOne) |
207 ((oldState & SH_State_RefCount) == SH_RefCountOne ? SH_State_Closed : 0) |
208 (fDispose ? SH_State_Disposed : 0);
209
210 } while (InterlockedCompareExchange((LONG*)&sh->m_state, newState, oldState) != oldState);
211
212 // If we get here we successfully decremented the ref count. Additionally we
213 // may have decremented it to zero and set the handle state as closed. In
214 // this case (providng we own the handle) we will call the ReleaseHandle
215 // method on the SafeHandle subclass.
216 if (fPerformRelease)
217 RunReleaseMethod((SafeHandle*) OBJECTREFToObject(sh));
218}
219
220void SafeHandle::Dispose()
221{
222 CONTRACTL {
223 THROWS;
224 GC_TRIGGERS;
225 MODE_COOPERATIVE;
226 INSTANCE_CHECK;
227 } CONTRACTL_END;
228
229 // You can't use the "this" pointer after the call to Release because
230 // Release may trigger a GC.
231 SAFEHANDLEREF sh(this);
232
233 _ASSERTE(sh->IsFullyInitialized());
234
235 GCPROTECT_BEGIN(sh);
236 sh->Release(true);
237 // Suppress finalization on this object (we may be racing here but the
238 // operation below is idempotent and a dispose should never race a
239 // finalization).
240 GCHeapUtilities::GetGCHeap()->SetFinalizationRun(OBJECTREFToObject(sh));
241 GCPROTECT_END();
242}
243
244void SafeHandle::SetHandle(LPVOID handle)
245{
246 CONTRACTL {
247 THROWS;
248 MODE_COOPERATIVE;
249 INSTANCE_CHECK;
250 SO_TOLERANT;
251 } CONTRACTL_END;
252
253 _ASSERTE(IsFullyInitialized());
254
255 // The SafeHandle's handle field can only be set it if the SafeHandle isn't
256 // closed or disposed and its ref count is 1.
257 if (m_state != (LONG)SH_RefCountOne)
258 COMPlusThrow(kObjectDisposedException, IDS_EE_SAFEHANDLECANNOTSETHANDLE);
259
260 m_handle = handle;
261}
262
263void AcquireSafeHandle(SAFEHANDLEREF* s)
264{
265 WRAPPER_NO_CONTRACT;
266 GCX_COOP();
267 _ASSERTE(s != NULL && *s != NULL);
268 (*s)->AddRef();
269}
270
271void ReleaseSafeHandle(SAFEHANDLEREF* s)
272{
273 WRAPPER_NO_CONTRACT;
274 GCX_COOP();
275 _ASSERTE(s != NULL && *s != NULL);
276 (*s)->Release(false);
277}
278
279
280// This could theoretically be an instance method, but we'd need to
281// somehow GC protect the this pointer or never dereference any
282// field within the object. It's a lot simpler if we simply make
283// this method static.
284void SafeHandle::RunReleaseMethod(SafeHandle* psh)
285{
286 CONTRACTL {
287 THROWS;
288 GC_TRIGGERS;
289 MODE_COOPERATIVE;
290 } CONTRACTL_END;
291
292 SAFEHANDLEREF sh(psh);
293 _ASSERTE(sh != NULL);
294 _ASSERTE(sh->m_ownsHandle);
295 _ASSERTE(sh->IsFullyInitialized());
296
297 GCPROTECT_BEGIN(sh);
298
299 // Save last error from P/Invoke in case the implementation of ReleaseHandle
300 // trashes it (important because this ReleaseHandle could occur implicitly
301 // as part of unmarshaling another P/Invoke).
302 Thread *pThread = GetThread();
303 DWORD dwSavedError = pThread->m_dwLastError;
304
305 CLR_BOOL fReleaseHandle = FALSE;
306
307 DECLARE_ARGHOLDER_ARRAY(args, 1);
308 args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(sh);
309
310 PREPARE_SIMPLE_VIRTUAL_CALLSITE_USING_SLOT(s_ReleaseHandleMethodSlot, sh);
311
312 CRITICAL_CALLSITE;
313 CALL_MANAGED_METHOD(fReleaseHandle, CLR_BOOL, args);
314
315 if (!fReleaseHandle) {
316#ifdef MDA_SUPPORTED
317 MDA_TRIGGER_ASSISTANT(ReleaseHandleFailed, ReportViolation(sh->GetTypeHandle(), sh->m_handle));
318#endif
319 }
320
321 pThread->m_dwLastError = dwSavedError;
322
323 GCPROTECT_END();
324}
325
326FCIMPL1(void, SafeHandle::DisposeNative, SafeHandle* refThisUNSAFE)
327{
328 FCALL_CONTRACT;
329
330 SAFEHANDLEREF sh(refThisUNSAFE);
331 if (sh == NULL)
332 FCThrowVoid(kNullReferenceException);
333
334 HELPER_METHOD_FRAME_BEGIN_1(sh);
335 _ASSERTE(sh->IsFullyInitialized());
336 sh->Dispose();
337 HELPER_METHOD_FRAME_END();
338}
339FCIMPLEND
340
341FCIMPL1(void, SafeHandle::Finalize, SafeHandle* refThisUNSAFE)
342{
343 FCALL_CONTRACT;
344
345 SAFEHANDLEREF sh(refThisUNSAFE);
346 _ASSERTE(sh != NULL);
347
348 HELPER_METHOD_FRAME_BEGIN_1(sh);
349
350 if (sh->IsFullyInitialized())
351 sh->Dispose();
352
353 // By the time we get here we better have gotten rid of any handle resources
354 // we own (unless we were force finalized during shutdown).
355
356 // It's possible to have a critical finalizer reference a
357 // safehandle that ends up calling DangerousRelease *after* this finalizer
358 // is run. In that case we assert since the state is not closed.
359// _ASSERTE(!sh->IsFullyInitialized() || (sh->m_state & SH_State_Closed) || g_fEEShutDown);
360
361 HELPER_METHOD_FRAME_END();
362}
363FCIMPLEND
364
365FCIMPL1(void, SafeHandle::SetHandleAsInvalid, SafeHandle* refThisUNSAFE)
366{
367 FCALL_CONTRACT;
368
369 SAFEHANDLEREF sh(refThisUNSAFE);
370 _ASSERTE(sh != NULL);
371
372 // Attempt to set closed state (low order bit of the m_state field).
373 // Might have to attempt these repeatedly, if the operation suffers
374 // interference from an AddRef or Release.
375 INT32 oldState, newState;
376 do {
377
378 // First step is to read the current handle state so we can predicate a
379 // state update on it.
380 oldState = sh->m_state;
381
382 // New state has the same ref count but is now closed. Attempt to write
383 // this new state but fail if the state was updated in the meantime.
384 newState = oldState | SH_State_Closed;
385
386 } while (InterlockedCompareExchange((LONG*)&sh->m_state, newState, oldState) != oldState);
387
388 GCHeapUtilities::GetGCHeap()->SetFinalizationRun(OBJECTREFToObject(sh));
389}
390FCIMPLEND
391
392FCIMPL2(void, SafeHandle::DangerousAddRef, SafeHandle* refThisUNSAFE, CLR_BOOL *pfSuccess)
393{
394 FCALL_CONTRACT;
395
396 SAFEHANDLEREF sh(refThisUNSAFE);
397
398 HELPER_METHOD_FRAME_BEGIN_1(sh);
399
400 if (pfSuccess == NULL)
401 COMPlusThrow(kNullReferenceException);
402
403 sh->AddRef();
404 *pfSuccess = TRUE;
405
406 HELPER_METHOD_FRAME_END();
407}
408FCIMPLEND
409
410FCIMPL1(void, SafeHandle::DangerousRelease, SafeHandle* refThisUNSAFE)
411{
412 FCALL_CONTRACT;
413
414 SAFEHANDLEREF sh(refThisUNSAFE);
415
416 HELPER_METHOD_FRAME_BEGIN_1(sh);
417
418 sh->Release(FALSE);
419
420 HELPER_METHOD_FRAME_END();
421}
422FCIMPLEND
423
424FCIMPL1(void, CriticalHandle::FireCustomerDebugProbe, CriticalHandle* refThisUNSAFE)
425{
426 FCALL_CONTRACT;
427
428 CRITICALHANDLEREF ch(refThisUNSAFE);
429
430 HELPER_METHOD_FRAME_BEGIN_1(ch);
431
432#ifdef MDA_SUPPORTED
433 MDA_TRIGGER_ASSISTANT(ReleaseHandleFailed, ReportViolation(ch->GetTypeHandle(), ch->m_handle));
434#else
435 FCUnique(0x53);
436#endif
437
438 HELPER_METHOD_FRAME_END();
439}
440FCIMPLEND
441