/* * Copyright 2019-2023 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include #if defined(OPENSSL_THREADS_WINNT) # include # include static unsigned __stdcall thread_start_thunk(LPVOID vthread) { CRYPTO_THREAD *thread; CRYPTO_THREAD_RETVAL ret; thread = (CRYPTO_THREAD *)vthread; thread->thread_id = GetCurrentThreadId(); ret = thread->routine(thread->data); ossl_crypto_mutex_lock(thread->statelock); CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_FINISHED); thread->retval = ret; ossl_crypto_condvar_signal(thread->condvar); ossl_crypto_mutex_unlock(thread->statelock); return 0; } int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) { HANDLE *handle; handle = OPENSSL_zalloc(sizeof(*handle)); if (handle == NULL) goto fail; *handle = (HANDLE)_beginthreadex(NULL, 0, &thread_start_thunk, thread, 0, NULL); if (*handle == NULL) goto fail; thread->handle = handle; return 1; fail: thread->handle = NULL; OPENSSL_free(handle); return 0; } int ossl_crypto_thread_native_perform_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) { DWORD thread_retval; HANDLE *handle; if (thread == NULL || thread->handle == NULL) return 0; handle = (HANDLE *) thread->handle; if (WaitForSingleObject(*handle, INFINITE) != WAIT_OBJECT_0) return 0; if (GetExitCodeThread(*handle, &thread_retval) == 0) return 0; /* * GetExitCodeThread call followed by this check is to make sure that * the thread exited properly. In particular, thread_retval may be * non-zero when exited via explicit ExitThread/TerminateThread or * if the thread is still active (returns STILL_ACTIVE (259)). */ if (thread_retval != 0) return 0; if (CloseHandle(*handle) == 0) return 0; return 1; } int ossl_crypto_thread_native_exit(void) { _endthreadex(0); return 1; } int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) { return thread->thread_id == GetCurrentThreadId(); } CRYPTO_MUTEX *ossl_crypto_mutex_new(void) { CRITICAL_SECTION *mutex; if ((mutex = OPENSSL_zalloc(sizeof(*mutex))) == NULL) return NULL; InitializeCriticalSection(mutex); return (CRYPTO_MUTEX *)mutex; } void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) { CRITICAL_SECTION *mutex_p; mutex_p = (CRITICAL_SECTION *)mutex; EnterCriticalSection(mutex_p); } int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) { CRITICAL_SECTION *mutex_p; mutex_p = (CRITICAL_SECTION *)mutex; if (TryEnterCriticalSection(mutex_p)) return 1; return 0; } void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) { CRITICAL_SECTION *mutex_p; mutex_p = (CRITICAL_SECTION *)mutex; LeaveCriticalSection(mutex_p); } void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) { CRITICAL_SECTION **mutex_p; mutex_p = (CRITICAL_SECTION **)mutex; if (*mutex_p != NULL) DeleteCriticalSection(*mutex_p); OPENSSL_free(*mutex_p); *mutex = NULL; } static int determine_timeout(OSSL_TIME deadline, DWORD *w_timeout_p) { OSSL_TIME now, delta; uint64_t ms; if (ossl_time_is_infinite(deadline)) { *w_timeout_p = INFINITE; return 1; } now = ossl_time_now(); delta = ossl_time_subtract(deadline, now); if (ossl_time_is_zero(delta)) return 0; ms = ossl_time2ms(delta); /* * Amount of time we want to wait is too long for the 32-bit argument to * the Win32 API, so just wait as long as possible. */ if (ms > (uint64_t)(INFINITE - 1)) *w_timeout_p = INFINITE - 1; else *w_timeout_p = (DWORD)ms; return 1; } # if defined(OPENSSL_THREADS_WINNT_LEGACY) # include /* * Win32, before Vista, did not have an OS-provided condition variable * construct. This leads to the need to construct our own condition variable * construct in order to support Windows XP. * * It is difficult to construct a condition variable construct using the * OS-provided primitives in a way that is both correct (avoiding race * conditions where broadcasts get lost) and fair. * * CORRECTNESS: * A blocked thread is a thread which is calling wait(), between the * precise instants at which the external mutex passed to wait() is * unlocked and the instant at which it is relocked. * * a) * - If broadcast() is called, ALL blocked threads MUST be unblocked. * - If signal() is called, at least one blocked thread MUST be unblocked. * * (i.e.: a signal or broadcast must never get 'lost') * * b) * - If broadcast() or signal() is called, this must not cause a thread * which is not blocked to return immediately from a subsequent * call to wait(). * * FAIRNESS: * If broadcast() is called at time T1, all blocked threads must be unblocked * before any thread which subsequently calls wait() at time T2 > T1 is * unblocked. * * An example of an implementation which lacks fairness is as follows: * * t1 enters wait() * t2 enters wait() * * tZ calls broadcast() * * t1 exits wait() * t1 enters wait() * * tZ calls broadcast() * * t1 exits wait() * * IMPLEMENTATION: * * The most suitable primitives available to us in Windows XP are semaphores, * auto-reset events and manual-reset events. A solution based on semaphores * is chosen. * * PROBLEM. Designing a solution based on semaphores is non-trivial because, * while it is easy to track the number of waiters in an interlocked data * structure and then add that number to the semaphore, this does not * guarantee fairness or correctness. Consider the following situation: * * - t1 enters wait(), adding 1 to the wait counter & blocks on the semaphore * - t2 enters wait(), adding 1 to the wait counter & blocks on the semaphore * - tZ calls broadcast(), finds the wait counter is 2, adds 2 to the semaphore * * - t1 exits wait() * - t1 immediately reenters wait() and blocks on the semaphore * - The semaphore is still positive due to also having been signalled * for t2, therefore it is decremented * - t1 exits wait() immediately; t2 is never woken * * GENERATION COUNTERS. One naive solution to this is to use a generation * counter. Each broadcast() invocation increments a generation counter. If * the generation counter has not changed during a semaphore wait operation * inside wait(), this indicates that no broadcast() call has been made in * the meantime; therefore, the successful semaphore decrement must have * 'stolen' a wakeup from another thread which was waiting to wakeup from the * prior broadcast() call but which had not yet had a chance to do so. The * semaphore can then be reincremented and the wait() operation repeated. * * However, this suffers from the obvious problem that without OS guarantees * as to how semaphore readiness events are distributed amongst threads, * there is no particular guarantee that the semaphore readiness event will * not be immediately redistributed back to the same thread t1. * * SOLUTION. A solution is chosen as follows. In its initial state, a * condition variable can accept waiters, who wait for the semaphore * normally. However, once broadcast() is called, the condition * variable becomes 'closed'. Any existing blocked threads are unblocked, * but any new calls to wait() will instead enter a blocking pre-wait stage. * Pre-wait threads are not considered to be waiting (and the external * mutex remains held). A call to wait() in pre-wait cannot progress * to waiting until all threads due to be unblocked by the prior broadcast() * call have returned and had a chance to execute. * * This pre-wait does not affect a thread if it does not call wait() * again until after all threads have had a chance to execute. * * RESOURCE USAGE. Aside from an allocation for the condition variable * structure, this solution uses two Win32 semaphores. * * FUTURE OPTIMISATIONS: * * An optimised multi-generation implementation is possible at the cost of * higher Win32 resource usage. Multiple 'buckets' could be defined, with * usage rotating between buckets internally as buckets become closed. * This would avoid the need for the prewait in more cases, depending * on intensity of usage. * */ typedef struct legacy_condvar_st { CRYPTO_MUTEX *int_m; /* internal mutex */ HANDLE sema; /* main wait semaphore */ HANDLE prewait_sema; /* prewait semaphore */ /* * All of the following fields are protected by int_m. * * num_wake only ever increases by virtue of a corresponding decrease in * num_wait. num_wait can decrease for other reasons (for example due to a * wait operation timing out). */ size_t num_wait; /* Num. threads currently blocked */ size_t num_wake; /* Num. threads due to wake up */ size_t num_prewait; /* Num. threads in prewait */ size_t gen; /* Prewait generation */ int closed; /* Is closed? */ } LEGACY_CONDVAR; CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) { LEGACY_CONDVAR *cv; if ((cv = OPENSSL_malloc(sizeof(LEGACY_CONDVAR))) == NULL) return NULL; if ((cv->int_m = ossl_crypto_mutex_new()) == NULL) { OPENSSL_free(cv); return NULL; } if ((cv->sema = CreateSemaphoreA(NULL, 0, LONG_MAX, NULL)) == NULL) { ossl_crypto_mutex_free(&cv->int_m); OPENSSL_free(cv); return NULL; } if ((cv->prewait_sema = CreateSemaphoreA(NULL, 0, LONG_MAX, NULL)) == NULL) { CloseHandle(cv->sema); ossl_crypto_mutex_free(&cv->int_m); OPENSSL_free(cv); return NULL; } cv->num_wait = 0; cv->num_wake = 0; cv->num_prewait = 0; cv->closed = 0; return (CRYPTO_CONDVAR *)cv; } void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv_p) { if (*cv_p != NULL) { LEGACY_CONDVAR *cv = *(LEGACY_CONDVAR **)cv_p; CloseHandle(cv->sema); CloseHandle(cv->prewait_sema); ossl_crypto_mutex_free(&cv->int_m); OPENSSL_free(cv); } *cv_p = NULL; } static uint32_t obj_wait(HANDLE h, OSSL_TIME deadline) { DWORD timeout; if (!determine_timeout(deadline, &timeout)) timeout = 1; return WaitForSingleObject(h, timeout); } void ossl_crypto_condvar_wait_timeout(CRYPTO_CONDVAR *cv_, CRYPTO_MUTEX *ext_m, OSSL_TIME deadline) { LEGACY_CONDVAR *cv = (LEGACY_CONDVAR *)cv_; int closed, set_prewait = 0, have_orig_gen = 0; uint32_t rc; size_t orig_gen; /* Admission control - prewait until we can enter our actual wait phase. */ do { ossl_crypto_mutex_lock(cv->int_m); closed = cv->closed; /* * Once prewait is over the prewait semaphore is signalled and * num_prewait is set to 0. Use a generation counter to track if we need * to remove a value we added to num_prewait when exiting (e.g. due to * timeout or failure of WaitForSingleObject). */ if (!have_orig_gen) { orig_gen = cv->gen; have_orig_gen = 1; } else if (cv->gen != orig_gen) { set_prewait = 0; orig_gen = cv->gen; } if (!closed) { /* We can now be admitted. */ ++cv->num_wait; if (set_prewait) { --cv->num_prewait; set_prewait = 0; } } else if (!set_prewait) { ++cv->num_prewait; set_prewait = 1; } ossl_crypto_mutex_unlock(cv->int_m); if (closed) if (obj_wait(cv->prewait_sema, deadline) != WAIT_OBJECT_0) { /* * If we got WAIT_OBJECT_0 we are safe - num_prewait has been * set to 0 and the semaphore has been consumed. On the other * hand if we timed out, there may be a residual posting that * was made just after we timed out. However in the worst case * this will just cause an internal spurious wakeup here in the * future, so we do not care too much about this. We treat * failure and timeout cases as the same, and simply exit in * this case. */ ossl_crypto_mutex_lock(cv->int_m); if (set_prewait && cv->gen == orig_gen) --cv->num_prewait; ossl_crypto_mutex_unlock(cv->int_m); return; } } while (closed); /* * Unlock external mutex. Do not do this until we have been admitted, as we * must guarantee we wake if broadcast is called at any time after ext_m is * unlocked. */ ossl_crypto_mutex_unlock(ext_m); for (;;) { /* Wait. */ rc = obj_wait(cv->sema, deadline); /* Reacquire internal mutex and probe state. */ ossl_crypto_mutex_lock(cv->int_m); if (cv->num_wake > 0) { /* * A wake token is available, so we can wake up. Consume the token * and get out of here. We don't care what WaitForSingleObject * returned here (e.g. if it timed out coincidentally). In the * latter case a signal might be left in the semaphore which causes * a future WaitForSingleObject call to return immediately, but in * this case we will just loop again. */ --cv->num_wake; if (cv->num_wake == 0 && cv->closed) { /* * We consumed the last wake token, so we can now open the * condition variable for new admissions. */ cv->closed = 0; if (cv->num_prewait > 0) { ReleaseSemaphore(cv->prewait_sema, (LONG)cv->num_prewait, NULL); cv->num_prewait = 0; ++cv->gen; } } } else if (rc == WAIT_OBJECT_0) { /* * We got a wakeup from the semaphore but we did not have any wake * tokens. This ideally does not happen, but might if during a * previous wait() call the semaphore is posted just after * WaitForSingleObject returns due to a timeout (such that the * num_wake > 0 case is taken above). Just spin again. (It is worth * noting that repeated WaitForSingleObject calls is the only method * documented for decrementing a Win32 semaphore, so this is * basically the best possible strategy.) */ ossl_crypto_mutex_unlock(cv->int_m); continue; } else { /* * Assume we timed out. The WaitForSingleObject call may also have * failed for some other reason, which we treat as a timeout. */ assert(cv->num_wait > 0); --cv->num_wait; } break; } ossl_crypto_mutex_unlock(cv->int_m); ossl_crypto_mutex_lock(ext_m); } void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *ext_m) { ossl_crypto_condvar_wait_timeout(cv, ext_m, ossl_time_infinite()); } void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv_) { LEGACY_CONDVAR *cv = (LEGACY_CONDVAR *)cv_; size_t num_wake; ossl_crypto_mutex_lock(cv->int_m); num_wake = cv->num_wait; if (num_wake == 0) { ossl_crypto_mutex_unlock(cv->int_m); return; } cv->num_wake += num_wake; cv->num_wait -= num_wake; cv->closed = 1; ossl_crypto_mutex_unlock(cv->int_m); ReleaseSemaphore(cv->sema, num_wake, NULL); } void ossl_crypto_condvar_signal(CRYPTO_CONDVAR *cv_) { LEGACY_CONDVAR *cv = (LEGACY_CONDVAR *)cv_; ossl_crypto_mutex_lock(cv->int_m); if (cv->num_wait == 0) { ossl_crypto_mutex_unlock(cv->int_m); return; } /* * We do not close the condition variable when merely signalling, as there * are no guaranteed fairness semantics here, unlike for a broadcast. */ --cv->num_wait; ++cv->num_wake; ossl_crypto_mutex_unlock(cv->int_m); ReleaseSemaphore(cv->sema, 1, NULL); } # else CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) { CONDITION_VARIABLE *cv_p; if ((cv_p = OPENSSL_zalloc(sizeof(*cv_p))) == NULL) return NULL; InitializeConditionVariable(cv_p); return (CRYPTO_CONDVAR *)cv_p; } void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) { CONDITION_VARIABLE *cv_p; CRITICAL_SECTION *mutex_p; cv_p = (CONDITION_VARIABLE *)cv; mutex_p = (CRITICAL_SECTION *)mutex; SleepConditionVariableCS(cv_p, mutex_p, INFINITE); } void ossl_crypto_condvar_wait_timeout(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex, OSSL_TIME deadline) { DWORD timeout; CONDITION_VARIABLE *cv_p = (CONDITION_VARIABLE *)cv; CRITICAL_SECTION *mutex_p = (CRITICAL_SECTION *)mutex; if (!determine_timeout(deadline, &timeout)) timeout = 1; SleepConditionVariableCS(cv_p, mutex_p, timeout); } void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) { CONDITION_VARIABLE *cv_p; cv_p = (CONDITION_VARIABLE *)cv; WakeAllConditionVariable(cv_p); } void ossl_crypto_condvar_signal(CRYPTO_CONDVAR *cv) { CONDITION_VARIABLE *cv_p; cv_p = (CONDITION_VARIABLE *)cv; WakeConditionVariable(cv_p); } void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) { CONDITION_VARIABLE **cv_p; cv_p = (CONDITION_VARIABLE **)cv; OPENSSL_free(*cv_p); *cv_p = NULL; } # endif void ossl_crypto_mem_barrier(void) { MemoryBarrier(); } #endif