A deadlock occurs when two or more threads or processes become stuck waiting for resources that each other holds, preventing any of them from progressing. The…
A deadlock occurs when two or more threads or processes become stuck waiting for resources that each other holds, preventing any of them from progressing. The application appears to hang or freeze, becoming unresponsive to user requests. While deadlocks are primarily a reliability and availability issue rather than a direct security vulnerability, they can be exploited to cause denial of service or can mask other security problems.
02How It Happens
Deadlocks arise when concurrent code acquires locks or resources in an inconsistent order. For example, Thread A locks Resource 1 and then waits for Resource 2, while Thread B locks Resource 2 and waits for Resource 1. Neither thread can proceed because each is blocked waiting for a resource the other holds. This is most common in multi-threaded applications that use mutexes, semaphores, database locks, or file locks without careful coordination. The problem is often hidden in normal operation and only surfaces under specific timing conditions or high concurrency load.
03Real-World Impact
A deadlock causes the affected application or service to become unresponsive, effectively creating a denial of service condition. Users cannot complete transactions, requests time out, and the application may need to be forcibly restarted. In systems handling critical operations—such as payment processing, authentication, or data synchronization—a deadlock can disrupt business continuity. Attackers may intentionally trigger deadlock conditions by crafting requests that force specific lock acquisition patterns, or deadlocks may be discovered and exploited as a side effect of other concurrency bugs.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread_1_work():
with lock_a:
print("Thread 1 acquired lock_a")
time.sleep(0.1) # Simulate work
with lock_b: # Waits for lock_b
print("Thread 1 acquired lock_b")
def thread_2_work():
with lock_b:
print("Thread 2 acquired lock_b")
time.sleep(0.1) # Simulate work
with lock_a: # Waits for lock_a
print("Thread 2 acquired lock_a")
t1 = threading.Thread(target=thread_1_work)
t2 = threading.Thread(target=thread_2_work)
t1.start()
t2.start()
Why it's vulnerable: Thread 1 acquires lock_a then waits for lock_b, while Thread 2 acquires lock_b then waits for lock_a. Both threads block indefinitely, creating a deadlock.
Fixed pattern
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread_1_work():
with lock_a:
with lock_b: # Always acquire in the same order
print("Thread 1 acquired both locks")
def thread_2_work():
with lock_a: # Acquire lock_a first, same as thread_1
with lock_b:
print("Thread 2 acquired both locks")
t1 = threading.Thread(target=thread_1_work)
t2 = threading.Thread(target=thread_2_work)
t1.start()
t2.start()
Vulnerable pattern
<?php
// Simulated database locks (pseudo-code)
$pdo = new PDO('sqlite::memory:');
function process_transfer_1($pdo) {
$pdo->exec('BEGIN EXCLUSIVE');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
sleep(1); // Simulate work
$pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
$pdo->exec('COMMIT');
}
function process_transfer_2($pdo) {
$pdo->exec('BEGIN EXCLUSIVE');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 2');
sleep(1); // Simulate work
$pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 1');
$pdo->exec('COMMIT');
}
?>
Why it's vulnerable: If two concurrent requests execute these functions simultaneously, they may lock accounts in opposite orders, causing each transaction to wait indefinitely for the other to release its lock.
Fixed pattern
<?php
$pdo = new PDO('sqlite::memory:');
function process_transfer_safe($pdo, $from_id, $to_id) {
// Always lock accounts in the same order (by ID)
$first_id = min($from_id, $to_id);
$second_id = max($from_id, $to_id);
$pdo->exec('BEGIN EXCLUSIVE');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = ' . $first_id);
$pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = ' . $second_id);
$pdo->exec('COMMIT');
}
?>
05Prevention Checklist
Establish a lock ordering policy: Document the order in which all locks must be acquired across the entire codebase and enforce it consistently.
Use timeouts on lock acquisition: Set a maximum wait time for locks; if exceeded, release all held locks and retry or fail gracefully rather than blocking indefinitely.
Minimize lock scope: Hold locks for the shortest time possible and avoid acquiring new locks while already holding others.
Avoid nested locks when possible: Refactor code to reduce the number of locks held simultaneously; use lock-free data structures or atomic operations where feasible.
Test under high concurrency: Run stress tests with many concurrent threads or processes to expose timing-dependent deadlock conditions before production.
Monitor for hangs: Log lock acquisition and release events; alert on threads that remain blocked for unexpectedly long periods.
06Signs You May Already Be Affected
Watch for application processes or threads that become unresponsive and do not recover without manual restart. Check system logs and application logs for threads stuck in "waiting" or "blocked" states, or for requests that time out consistently under load. If restarting the application temporarily resolves the issue but it recurs under similar conditions, deadlock is a likely cause.