diff options
author | Abseil Team <absl-team@google.com> | 2022-05-17 01:44:42 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2022-05-17 01:45:38 -0700 |
commit | 9444b11e0c4e1f079c87067b5bbab1c5ff718809 (patch) | |
tree | d533400407231d6e74c0f21883b4e6a9dea033ee /absl/synchronization/internal/waiter.h | |
parent | aac2279f22eef04d01fc42e66fc183a32f08a9b4 (diff) | |
download | abseil-9444b11e0c4e1f079c87067b5bbab1c5ff718809.tar.gz abseil-9444b11e0c4e1f079c87067b5bbab1c5ff718809.tar.bz2 abseil-9444b11e0c4e1f079c87067b5bbab1c5ff718809.zip |
absl: fix use-after-free in Mutex/CondVar
Both Mutex and CondVar signal PerThreadSem/Waiter after satisfying the wait condition,
as the result the waiting thread may return w/o waiting on the
PerThreadSem/Waiter at all. If the waiting thread then exits, it currently
destroys Waiter object. As the result Waiter::Post can be called on
already destroyed object.
PerThreadSem/Waiter must be type-stable after creation and must not be destroyed.
The futex-based implementation is the only one that is not affected by the bug
since there is effectively nothing to destroy (maybe only UBSan/ASan
could complain about calling methods on a destroyed object).
Here is the problematic sequence of events:
1: void Mutex::Block(PerThreadSynch *s) {
2: while (s->state.load(std::memory_order_acquire) == PerThreadSynch::kQueued) {
3: if (!DecrementSynchSem(this, s, s->waitp->timeout)) {
4: PerThreadSynch *Mutex::Wakeup(PerThreadSynch *w) {
5: ...
6: w->state.store(PerThreadSynch::kAvailable, std::memory_order_release);
7: IncrementSynchSem(this, w);
8: ...
9: }
Consider line 6 is executed, then line 2 observes kAvailable and
line 3 is not called. The thread executing Mutex::Block returns from
the method, acquires the mutex, releases the mutex, exits and destroys
PerThreadSem/Waiter.
Now Mutex::Wakeup resumes and executes line 7 on the destroyed object. Boom!
CondVar uses a similar pattern.
Moreover the semaphore-based Waiter implementation is not even destruction-safe
(the Waiter cannot be used to signal own destruction). So even if Mutex/CondVar
would always pair Waiter::Post with Waiter::Wait before destroying PerThreadSem/Waiter,
it would still be subject to use-after-free bug on the semaphore.
PiperOrigin-RevId: 449159939
Change-Id: I497134fa8b6ce1294a422827c5f0de0e897cea31
Diffstat (limited to 'absl/synchronization/internal/waiter.h')
-rw-r--r-- | absl/synchronization/internal/waiter.h | 12 |
1 files changed, 9 insertions, 3 deletions
diff --git a/absl/synchronization/internal/waiter.h b/absl/synchronization/internal/waiter.h index 4f4f692a..b8adfeb5 100644 --- a/absl/synchronization/internal/waiter.h +++ b/absl/synchronization/internal/waiter.h @@ -71,9 +71,6 @@ class Waiter { Waiter(const Waiter&) = delete; Waiter& operator=(const Waiter&) = delete; - // Destroy any data to track waits. - ~Waiter(); - // Blocks the calling thread until a matching call to `Post()` or // `t` has passed. Returns `true` if woken (`Post()` called), // `false` on timeout. @@ -106,6 +103,12 @@ class Waiter { #endif private: + // The destructor must not be called since Mutex/CondVar + // can use PerThreadSem/Waiter after the thread exits. + // Waiter objects are embedded in ThreadIdentity objects, + // which are reused via a freelist and are never destroyed. + ~Waiter() = delete; + #if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX // Futexes are defined by specification to be 32-bits. // Thus std::atomic<int32_t> must be just an int32_t with lockfree methods. @@ -138,6 +141,9 @@ class Waiter { // We can't include Windows.h in our headers, so we use aligned character // buffers to define the storage of SRWLOCK and CONDITION_VARIABLE. + // SRW locks and condition variables do not need to be explicitly destroyed. + // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializesrwlock + // https://stackoverflow.com/questions/28975958/why-does-windows-have-no-deleteconditionvariable-function-to-go-together-with alignas(void*) unsigned char mu_storage_[sizeof(void*)]; alignas(void*) unsigned char cv_storage_[sizeof(void*)]; int waiter_count_; |