Unlock of a Resource that is not Locked
Description
Unlock of a Resource that is not Locked is a synchronization vulnerability where software attempts to unlock or release a lock on a resource that is not currently locked, or that is not locked by the current thread or process. Depending on the locking implementation, this can corrupt the lock's internal state, the associated resource, or metadata used for tracking locks. Some locking mechanisms maintain reference counts or ownership information that becomes corrupted when unlock is called inappropriately. This vulnerability often occurs in error handling paths where locks are released without verifying they were successfully acquired.
Risk
The consequences range from memory corruption to denial of service to potential code execution. When lock state is corrupted, subsequent legitimate lock operations may fail or behave unpredictably, causing crashes or hangs. In implementations where lock metadata is stored in memory, unlocking a non-locked resource can corrupt adjacent memory, potentially enabling exploitation. In kernel code, this vulnerability is particularly severe as it can lead to system instability or privilege escalation. Race conditions may also emerge when lock state becomes inconsistent, allowing concurrent access to protected resources.
Solution
Always verify that a lock is held before unlocking it. Track lock acquisition success with boolean flags or return value checks. Use RAII patterns (C++) or try-finally blocks (Java, Python) to ensure lock release only occurs when acquisition succeeded. Implement assertions in debug builds to verify lock ownership before release. Use lock abstractions that track ownership and detect double-unlock or unlock-without-lock errors. In C/C++, consider using static analysis tools to detect improper lock/unlock patterns. Review error handling paths carefully to ensure locks are only released when actually held.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - Corrupted lock state can cause crashes or system hangs when subsequent lock operations fail. |
| Integrity | Scope: Integrity Modify Memory - Unlocking mechanisms may corrupt memory during execution when operating on non-locked resources. |
| Integrity, Confidentiality, Availability | Scope: Integrity, Confidentiality, Availability Execute Unauthorized Code or Commands - In certain implementations, memory corruption from improper unlock could enable code execution. |
Example Code
Vulnerable Code
// Vulnerable: Unlocking in error path without verifying lock was acquired
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int vulnerable_process(void *data) {
int result;
// Attempt to lock - might fail
result = pthread_mutex_trylock(&mutex);
// Process data
if (process_data(data) < 0) {
// Vulnerable: Unlocking even if trylock failed!
pthread_mutex_unlock(&mutex);
return -1;
}
// Vulnerable: Also unlocking here regardless of lock success
pthread_mutex_unlock(&mutex);
return 0;
}
// Vulnerable: Double unlock
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void vulnerable_cleanup(int error_occurred) {
if (error_occurred) {
// First unlock in error path
pthread_mutex_unlock(&lock);
}
// Vulnerable: Unconditional unlock - may be second unlock!
pthread_mutex_unlock(&lock);
}
# Vulnerable: Unlock without matching lock
import threading
lock = threading.Lock()
def vulnerable_function(should_lock):
if should_lock:
lock.acquire()
do_work()
# Vulnerable: Always releases, even if never acquired
lock.release() # RuntimeError if lock wasn't acquired
// Vulnerable: Unlocking in finally without checking if locked
import java.util.concurrent.locks.ReentrantLock;
public class VulnerableUnlock {
private final ReentrantLock lock = new ReentrantLock();
public void vulnerableMethod() {
try {
// tryLock might return false
boolean acquired = lock.tryLock();
doWork();
} finally {
// Vulnerable: Unlocks even if tryLock returned false
lock.unlock(); // IllegalMonitorStateException if not held
}
}
}
// Vulnerable: Kernel code unlocking without holding lock
void vulnerable_kernel_handler(struct resource *res) {
if (error_condition) {
// Vulnerable: Unlocking resource we don't hold lock on
spin_unlock(&res->lock);
return;
}
spin_lock(&res->lock);
// ... process ...
spin_unlock(&res->lock);
}
// Vulnerable: Conditional lock acquisition, unconditional release
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void vulnerable_read(int use_lock) {
if (use_lock) {
pthread_rwlock_rdlock(&rwlock);
}
read_data();
// Vulnerable: Unconditional unlock
pthread_rwlock_unlock(&rwlock); // Undefined if use_lock was false
}
Fixed Code
// Fixed: Track lock state and verify before unlock
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int fixed_process(void *data) {
int result;
int lock_held = 0; // Fixed: Track lock state
result = pthread_mutex_trylock(&mutex);
if (result == 0) {
lock_held = 1; // Lock successfully acquired
}
if (process_data(data) < 0) {
// Fixed: Only unlock if we hold the lock
if (lock_held) {
pthread_mutex_unlock(&mutex);
}
return -1;
}
// Fixed: Only unlock if we hold the lock
if (lock_held) {
pthread_mutex_unlock(&mutex);
}
return 0;
}
// Fixed: Single unlock point, controlled by flag
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void fixed_cleanup(int error_occurred, int lock_was_held) {
// Fixed: Only unlock if we actually hold the lock
if (lock_was_held) {
pthread_mutex_unlock(&lock);
}
}
# Fixed: Use context manager to ensure proper lock handling
import threading
lock = threading.Lock()
def fixed_function(should_lock):
if should_lock:
# Fixed: Context manager ensures proper acquire/release pairing
with lock:
do_work()
else:
do_work()
# Alternative: Track lock state explicitly
def fixed_function_explicit(should_lock):
acquired = False
try:
if should_lock:
lock.acquire()
acquired = True
do_work()
finally:
# Fixed: Only release if we acquired
if acquired:
lock.release()
// Fixed: Check lock ownership before unlock
import java.util.concurrent.locks.ReentrantLock;
public class FixedUnlock {
private final ReentrantLock lock = new ReentrantLock();
public void fixedMethod() {
boolean acquired = false;
try {
acquired = lock.tryLock();
doWork();
} finally {
// Fixed: Only unlock if we successfully acquired
if (acquired) {
lock.unlock();
}
}
}
// Better: Use try-with-resources pattern
public void betterMethod() {
lock.lock(); // Blocking lock guarantees acquisition
try {
doWork();
} finally {
lock.unlock(); // Safe because lock() succeeded
}
}
}
// Fixed: Kernel code with proper lock tracking
void fixed_kernel_handler(struct resource *res) {
int holding_lock = 0;
if (error_condition) {
// Fixed: Don't unlock if we don't hold the lock
return;
}
spin_lock(&res->lock);
holding_lock = 1;
// ... process ...
if (holding_lock) {
spin_unlock(&res->lock);
}
}
// Fixed: Match lock and unlock conditions
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void fixed_read(int use_lock) {
int lock_held = 0;
if (use_lock) {
pthread_rwlock_rdlock(&rwlock);
lock_held = 1;
}
read_data();
// Fixed: Only unlock if we acquired the lock
if (lock_held) {
pthread_rwlock_unlock(&rwlock);
}
}
// Best practice: RAII wrapper in C++
#include <mutex>
class FixedRAII {
std::mutex mutex_;
public:
void safeMethod() {
// std::lock_guard ensures unlock only if lock succeeded
std::lock_guard<std::mutex> guard(mutex_);
doWork();
} // Automatic unlock when guard goes out of scope
void tryLockMethod() {
// std::unique_lock with try_to_lock handles conditional locking
std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock);
if (lock.owns_lock()) {
doWork();
} // Only unlocks if lock was acquired
}
};
CVE Examples
- CVE-2010-4210: Kernel panic caused by unlocking a resource that was not locked during firmware loading.
- CVE-2008-4302: Improper unlock in filesystem code leading to system hang.
- CVE-2009-1243: Kernel vulnerability from unlocking non-locked spinlock.
Related CWEs
- CWE-667: Improper Locking (parent)
- CWE-765: Multiple Unlocks of a Critical Resource (related)
- CWE-764: Multiple Locks of a Critical Resource (related)
- CWE-755: Improper Handling of Exceptional Conditions (related)
References
- MITRE Corporation. "CWE-832: Unlock of a Resource that is not Locked." https://cwe.mitre.org/data/definitions/832.html
- CERT C Secure Coding Standard. "CON31-C. Do not destroy a mutex while it is locked."
- Linux Kernel Documentation. "Locking."