Improper locking occurs when software fails to correctly acquire or release locks on shared resources accessed by multiple threads or processes. This weakness…
Improper locking occurs when software fails to correctly acquire or release locks on shared resources accessed by multiple threads or processes. This weakness can lead to race conditions (where the outcome depends on unpredictable timing) or deadlocks (where threads wait indefinitely for each other). Both scenarios can cause data corruption, inconsistent state, or application hangs.
02How It Happens
In multi-threaded or multi-process environments, shared resources (files, database connections, in-memory data structures, counters) must be protected so that only one thread or process accesses them at a time. Improper locking occurs when:
- A lock is never acquired before accessing a shared resource, leaving it unprotected.
- A lock is acquired but never released, blocking other threads indefinitely.
- Locks are acquired in inconsistent order across different code paths, creating circular wait conditions (deadlock).
- A lock is released prematurely, before all critical operations on the shared resource are complete.
- The wrong synchronization primitive is used for the use case (e.g., a non-reentrant lock in a recursive function).
03Real-World Impact
Improper locking can cause data corruption when multiple threads write to the same variable or file simultaneously without coordination. Financial systems, inventory databases, and session stores are particularly vulnerable. In other cases, a missing lock can allow one thread to read partially-written data, leading to inconsistent application state. Deadlocks can freeze an application or cause it to become unresponsive, effectively creating a denial-of-service condition.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import threading
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
# Lock is never acquired
temp = counter
temp += 1
counter = temp
threads = [threading.Thread(target=increment_counter) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # Expected 100, but likely much lower due to race condition
Why it's vulnerable: Multiple threads read, modify, and write counter without synchronization. Between the read and write, another thread may modify the value, causing updates to be lost.
Fixed pattern
import threading
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
with lock:
temp = counter
temp += 1
counter = temp
threads = [threading.Thread(target=increment_counter) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # Reliably prints 100
Vulnerable pattern
<?php
// Simulating a shared counter in a file (common in PHP)
$counter_file = '/tmp/counter.txt';
function increment_counter() {
global $counter_file;
$current = (int)file_get_contents($counter_file);
$current++;
file_put_contents($counter_file, $current);
}
// Multiple concurrent requests call this function
// No locking — race condition between read and write
increment_counter();
?>
Why it's vulnerable: Two concurrent requests can both read the same value, increment it, and write back the same result, losing one increment. This is a classic read-modify-write race condition.
Identify shared resources: Document all data structures, files, and connections accessed by multiple threads or processes.
Use appropriate synchronization primitives: Apply mutexes, semaphores, or atomic operations to protect each shared resource. Choose the right tool for the job (e.g., threading.Lock for simple mutual exclusion, threading.RLock for reentrant scenarios).
Acquire locks before access, release after: Use language-level constructs (with statements in Python, flock() in PHP) to ensure locks are always released, even if an exception occurs.
Maintain consistent lock order: If multiple locks are needed, always acquire them in the same order across all code paths to prevent deadlocks.
Minimize critical sections: Hold locks for the shortest time possible. Avoid calling external functions or performing I/O while holding a lock.
Test under load: Use stress tests and thread sanitizers to detect race conditions and deadlocks before deployment.
06Signs You May Already Be Affected
- Inconsistent or corrupted data in files or databases that are frequently updated by concurrent processes.
- Application hangs or freezes that resolve only after a restart, suggesting a deadlock.
- Intermittent bugs that are difficult to reproduce, which often indicate race conditions that depend on timing.