| 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 |  | 
| 26 | WORD SafeHandle::s_IsInvalidHandleMethodSlot = MethodTable::NO_SLOT; | 
| 27 | WORD SafeHandle::s_ReleaseHandleMethodSlot = MethodTable::NO_SLOT; | 
| 28 |  | 
| 29 | void 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 |  | 
| 53 | void 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 |  | 
| 125 | void 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 |  | 
| 220 | void 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 |  | 
| 244 | void 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 |  | 
| 263 | void AcquireSafeHandle(SAFEHANDLEREF* s)  | 
| 264 | { | 
| 265 |     WRAPPER_NO_CONTRACT; | 
| 266 |     GCX_COOP(); | 
| 267 |     _ASSERTE(s != NULL && *s != NULL); | 
| 268 |     (*s)->AddRef();  | 
| 269 | } | 
| 270 |  | 
| 271 | void 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. | 
| 284 | void 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 |  | 
| 326 | FCIMPL1(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 | } | 
| 339 | FCIMPLEND | 
| 340 |  | 
| 341 | FCIMPL1(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 | } | 
| 363 | FCIMPLEND | 
| 364 |  | 
| 365 | FCIMPL1(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 | } | 
| 390 | FCIMPLEND | 
| 391 |  | 
| 392 | FCIMPL2(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 | } | 
| 408 | FCIMPLEND | 
| 409 |  | 
| 410 | FCIMPL1(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 | } | 
| 422 | FCIMPLEND | 
| 423 |  | 
| 424 | FCIMPL1(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 | } | 
| 440 | FCIMPLEND | 
| 441 |  |