Check using destroyed mutexes.
For apps built for Android < P, return EBUSY.
For apps built for Android >= P, abort.
This is to keep old apps work, and help debugging
apps built for >= P.
Bug: http://b/74632097
Test: run bionic-unit-tests.
Test: run bionic-benchmark.
Change-Id: I5271565a1a6ad12678f85d558a7f862a2b7aab4b
(cherry picked from commit 9651fdf93ae2c33fa38a6c5c5005b8596e716789)
diff --git a/libc/bionic/pthread_mutex.cpp b/libc/bionic/pthread_mutex.cpp
index f1f7294..9f9a0c2 100644
--- a/libc/bionic/pthread_mutex.cpp
+++ b/libc/bionic/pthread_mutex.cpp
@@ -40,7 +40,9 @@
#include "pthread_internal.h"
#include "private/bionic_constants.h"
+#include "private/bionic_fortify.h"
#include "private/bionic_futex.h"
+#include "private/bionic_sdk_version.h"
#include "private/bionic_systrace.h"
#include "private/bionic_time_conversions.h"
#include "private/bionic_tls.h"
@@ -171,7 +173,7 @@
return EBUSY;
}
-// Inlining this function in pthread_mutex_lock() add the cost of stack frame instructions on
+// Inlining this function in pthread_mutex_lock() adds the cost of stack frame instructions on
// ARM/ARM64, which increases at most 20 percent overhead. So make it noinline.
static int __attribute__((noinline)) PIMutexTimedLock(PIMutex& mutex,
const timespec* abs_timeout) {
@@ -413,7 +415,7 @@
#define MUTEX_TYPE_BITS_RECURSIVE MUTEX_TYPE_TO_BITS(PTHREAD_MUTEX_RECURSIVE)
#define MUTEX_TYPE_BITS_ERRORCHECK MUTEX_TYPE_TO_BITS(PTHREAD_MUTEX_ERRORCHECK)
// Use a special mutex type to mark priority inheritance mutexes.
-#define MUTEX_TYPE_BITS_WITH_PI MUTEX_TYPE_TO_BITS(3)
+#define PI_MUTEX_STATE MUTEX_TYPE_TO_BITS(3)
// For a PI mutex, it includes below fields:
// Atomic(uint16_t) state;
@@ -423,6 +425,7 @@
//
// bits: name description
// 15-14 type mutex type, should be 3
+// 13-0 padding should be 0
//
// pi_mutex holds the state of a PI mutex.
// pi_mutex_id holds an integer to find the state of a PI mutex.
@@ -533,7 +536,7 @@
}
mutex->pi_mutex_id = id;
#endif
- atomic_init(&mutex->state, MUTEX_TYPE_BITS_WITH_PI);
+ atomic_init(&mutex->state, PI_MUTEX_STATE);
PIMutex& pi_mutex = mutex->ToPIMutex();
pi_mutex.type = *attr & MUTEXATTR_TYPE_MASK;
pi_mutex.shared = (*attr & MUTEXATTR_SHARED_MASK) != 0;
@@ -777,6 +780,20 @@
} // namespace NonPI
+static inline __always_inline bool IsMutexDestroyed(uint16_t mutex_state) {
+ return mutex_state == 0xffff;
+}
+
+// Inlining this function in pthread_mutex_lock() adds the cost of stack frame instructions on
+// ARM64. So make it noinline.
+static int __attribute__((noinline)) HandleUsingDestroyedMutex(pthread_mutex_t* mutex,
+ const char* function_name) {
+ if (bionic_get_application_target_sdk_version() >= __ANDROID_API_P__) {
+ __fortify_fatal("%s called on a destroyed mutex (%p)", function_name, mutex);
+ }
+ return EBUSY;
+}
+
int pthread_mutex_lock(pthread_mutex_t* mutex_interface) {
#if !defined(__LP64__)
// Some apps depend on being able to pass NULL as a mutex and get EINVAL
@@ -788,7 +805,6 @@
#endif
pthread_mutex_internal_t* mutex = __get_internal_mutex(mutex_interface);
-
uint16_t old_state = atomic_load_explicit(&mutex->state, memory_order_relaxed);
uint16_t mtype = (old_state & MUTEX_TYPE_MASK);
// Avoid slowing down fast path of normal mutex lock operation.
@@ -797,7 +813,8 @@
if (__predict_true(NonPI::NormalMutexTryLock(mutex, shared) == 0)) {
return 0;
}
- } else if (mtype == MUTEX_TYPE_BITS_WITH_PI) {
+ }
+ if (old_state == PI_MUTEX_STATE) {
PIMutex& m = mutex->ToPIMutex();
// Handle common case first.
if (__predict_true(PIMutexTryLock(m) == 0)) {
@@ -805,6 +822,9 @@
}
return PIMutexTimedLock(mutex->ToPIMutex(), nullptr);
}
+ if (__predict_false(IsMutexDestroyed(old_state))) {
+ return HandleUsingDestroyedMutex(mutex_interface, __FUNCTION__);
+ }
return NonPI::MutexLockWithTimeout(mutex, false, nullptr);
}
@@ -819,7 +839,6 @@
#endif
pthread_mutex_internal_t* mutex = __get_internal_mutex(mutex_interface);
-
uint16_t old_state = atomic_load_explicit(&mutex->state, memory_order_relaxed);
uint16_t mtype = (old_state & MUTEX_TYPE_MASK);
uint16_t shared = (old_state & MUTEX_SHARED_MASK);
@@ -829,9 +848,12 @@
NonPI::NormalMutexUnlock(mutex, shared);
return 0;
}
- if (mtype == MUTEX_TYPE_BITS_WITH_PI) {
+ if (old_state == PI_MUTEX_STATE) {
return PIMutexUnlock(mutex->ToPIMutex());
}
+ if (__predict_false(IsMutexDestroyed(old_state))) {
+ return HandleUsingDestroyedMutex(mutex_interface, __FUNCTION__);
+ }
// Do we already own this recursive or error-check mutex?
pid_t tid = __get_thread()->tid;
@@ -875,9 +897,12 @@
uint16_t shared = (old_state & MUTEX_SHARED_MASK);
return NonPI::NormalMutexTryLock(mutex, shared);
}
- if (mtype == MUTEX_TYPE_BITS_WITH_PI) {
+ if (old_state == PI_MUTEX_STATE) {
return PIMutexTryLock(mutex->ToPIMutex());
}
+ if (__predict_false(IsMutexDestroyed(old_state))) {
+ return HandleUsingDestroyedMutex(mutex_interface, __FUNCTION__);
+ }
// Do we already own this recursive or error-check mutex?
pid_t tid = __get_thread()->tid;
@@ -934,21 +959,22 @@
return 0;
}
}
- if (mtype == MUTEX_TYPE_BITS_WITH_PI) {
+ if (old_state == PI_MUTEX_STATE) {
return PIMutexTimedLock(mutex->ToPIMutex(), abs_timeout);
}
+ if (__predict_false(IsMutexDestroyed(old_state))) {
+ return HandleUsingDestroyedMutex(mutex_interface, __FUNCTION__);
+ }
return NonPI::MutexLockWithTimeout(mutex, true, abs_timeout);
}
int pthread_mutex_destroy(pthread_mutex_t* mutex_interface) {
pthread_mutex_internal_t* mutex = __get_internal_mutex(mutex_interface);
uint16_t old_state = atomic_load_explicit(&mutex->state, memory_order_relaxed);
- if (old_state == 0xffff) {
- // The mutex has been destroyed.
- return EBUSY;
+ if (__predict_false(IsMutexDestroyed(old_state))) {
+ return HandleUsingDestroyedMutex(mutex_interface, __FUNCTION__);
}
- uint16_t mtype = (old_state & MUTEX_TYPE_MASK);
- if (mtype == MUTEX_TYPE_BITS_WITH_PI) {
+ if (old_state == PI_MUTEX_STATE) {
int result = PIMutexDestroy(mutex->ToPIMutex());
if (result == 0) {
mutex->FreePIMutex();
diff --git a/tests/pthread_test.cpp b/tests/pthread_test.cpp
index dd7879c..b34920e 100644
--- a/tests/pthread_test.cpp
+++ b/tests/pthread_test.cpp
@@ -1718,7 +1718,6 @@
void destroy() {
ASSERT_EQ(0, pthread_mutex_destroy(&lock));
- ASSERT_EQ(EBUSY, pthread_mutex_destroy(&lock));
}
DISALLOW_COPY_AND_ASSIGN(PthreadMutex);
@@ -2053,6 +2052,27 @@
ASSERT_EQ(0, pthread_mutex_unlock(&m.lock));
}
+TEST(pthread, pthread_mutex_using_destroyed_mutex) {
+#if defined(__BIONIC__)
+ pthread_mutex_t m;
+ ASSERT_EQ(0, pthread_mutex_init(&m, nullptr));
+ ASSERT_EQ(0, pthread_mutex_destroy(&m));
+ ASSERT_EXIT(pthread_mutex_lock(&m), ::testing::KilledBySignal(SIGABRT),
+ "pthread_mutex_lock called on a destroyed mutex");
+ ASSERT_EXIT(pthread_mutex_unlock(&m), ::testing::KilledBySignal(SIGABRT),
+ "pthread_mutex_unlock called on a destroyed mutex");
+ ASSERT_EXIT(pthread_mutex_trylock(&m), ::testing::KilledBySignal(SIGABRT),
+ "pthread_mutex_trylock called on a destroyed mutex");
+ timespec ts;
+ ASSERT_EXIT(pthread_mutex_timedlock(&m, &ts), ::testing::KilledBySignal(SIGABRT),
+ "pthread_mutex_timedlock called on a destroyed mutex");
+ ASSERT_EXIT(pthread_mutex_destroy(&m), ::testing::KilledBySignal(SIGABRT),
+ "pthread_mutex_destroy called on a destroyed mutex");
+#else
+ GTEST_LOG_(INFO) << "This test tests bionic pthread mutex implementation details.";
+#endif
+}
+
class StrictAlignmentAllocator {
public:
void* allocate(size_t size, size_t alignment) {