Double unlock
| Vulnerability potential | Low |
| DDoS potential | Medium |
The mutex is unlocked twice or more times successfully
Impact
A mutex is unlocked, then unlocked again while it is already free (or while owned
by a different thread). For a default pthread_mutex_t this is undefined
behavior. The dangerous outcome is not the redundant call itself but the window
it implies: the second unlock can release a lock that another thread has, in the
meantime, acquired — so two threads now believe they hold the same mutex and run
the protected critical section concurrently. That breaks mutual exclusion and
opens the door to data races and the corruption the mutex was meant to prevent.
Depending on the implementation the extra unlock may also leave the mutex’s
internal state inconsistent (e.g. a futex word or owner field driven below its
valid range), which can cause later lock operations to misbehave or crash.
Vulnerability potential
The defect is mainly an availability and correctness hazard, with a secondary memory-safety angle.
- Releasing a mutex another thread thinks it owns collapses mutual exclusion, producing data races on the guarded structure — a classic precursor to use-after-free, double-free or torn writes that can become exploitable.
- Corrupting the mutex’s internal bookkeeping can wedge subsequent locking, hanging threads that wait on it and denying service.
- The behavior is undefined, so on some implementations the second unlock simply crashes the process.
Technical details
POSIX states that unlocking a mutex the calling thread does not own, or unlocking an already-unlocked mutex, is undefined for a normal mutex. Implementations differ in how loudly they fail:
PTHREAD_MUTEX_NORMAL / default
No owner check. The second pthread_mutex_unlock may decrement or rewrite the
futex/owner state even though the mutex is free, leaving it corrupted; a
concurrently acquired lock can be spuriously released.
PTHREAD_MUTEX_ERRORCHECK
Records the owner and returns EPERM when a non-owner or repeated unlock is
attempted, making the bug detectable instead of silent.
PTHREAD_MUTEX_RECURSIVE
Maintains a count; one unlock too many drives the count negative or releases a level that was never taken, again undefined.
The same hazard exists for C++ std::mutex (unlocking when not owned is UB —
RAII std::lock_guard/std::unique_lock exist precisely to avoid manual
double-unlock) and Windows ReleaseMutex (which returns failure for a
non-owner).
Catching the issue
Runtime
ThreadSanitizer flags unlock of an unlocked mutex and mismatched
lock/unlock. Helgrind/DRD report releasing a mutex that is not held by the
caller. Use PTHREAD_MUTEX_ERRORCHECK in test builds and assert that unlock
returns 0.
Static analysis and design
Clang Thread Safety Analysis, Coverity and PVS-Studio track unbalanced
lock/unlock paths, which commonly arise from early return/goto in error
handling. Prefer RAII scope guards (C++) or pthread_cleanup_push so each
unlock is paired structurally with exactly one lock.
How to reproduce
Build with -fsanitize=thread; TSan reports the unlock of an already-unlocked
mutex.
#include <pthread.h>
#include <stdio.h>
int main(void)
{
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);
printf("rc of second unlock: %d\n", pthread_mutex_unlock(&m)); /* UB */
return 0;
}