Unsachgemäßes Sperren
Beschreibung
Unsachgemäßes Sperren tritt auf, wenn Software Synchronisierungsprimitive falsch verwendet, um den Zugriff auf gemeinsam genutzte Ressourcen zu kontrollieren. Dies umfasst das Versäumnis, Sperren vor dem Zugriff auf gemeinsam genutzte Daten zu erwerben, Sperren nach der Verwendung nicht freizugeben, falschen Sperrbereich zu verwenden, Sperren zu lange zu halten, Sperrreihenfolgeverletzungen die Deadlocks verursachen, und Versuche, bereits gehaltene nicht-reentrantfähige Sperren zu sperren. Unsachgemäßes Sperren führt zu Race Conditions, Deadlocks und Datenkorruption.
Risiko
Race Conditions ermöglichen Datenkorruption oder Sicherheitsumgehung. Deadlocks verursachen Denial of Service. Doppeltes Sperren bei nicht-reentrantfähigen Sperren verursacht Einfrieren. Fehlende Entsperrungen führen zu dauerhafter Ressourcenunverfügbarkeit. Sperrkonkurrenz verschlechtert die Leistung. Prioritätsinversion verursacht Systeminstabilität.
Lösung
Paaren Sie Sperrerwerbe immer mit Freigaben. Verwenden Sie RAII-Muster für automatische Entsperrung. Implementieren Sie konsistente Sperrreihenfolge, um Deadlocks zu verhindern. Verwenden Sie angemessene Sperrgranularität. Bevorzugen Sie höherstufige Nebenläufigkeitskonstrukte. Verwenden Sie Try-Lock mit Timeouts wo angemessen. Testen Sie mit Thread-Sanitizern.
Häufige Konsequenzen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Datenkorruption Unsynchronisierter Zugriff beschädigt gemeinsamen Zustand. |
| Verfügbarkeit | Bereich: Deadlock/DoS Sperrfehler verursachen Systemhänger. |
| Sicherheit | Bereich: Race Conditions Timing-Schwachstellen durch unsachgemäße Synchronisierung. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Unsachgemäßes Sperren in C
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;
// VERWUNDBAR: Fehlende Sperre
void increment_vulnerable() {
// VERWUNDBAR: Keine Sperre - Race Condition
shared_counter++;
}
// VERWUNDBAR: Sperre nie auf Fehlerpfad freigegeben
int process_data_vulnerable(int* data) {
pthread_mutex_lock(&lock);
if (data == NULL) {
// VERWUNDBAR: Sperre nicht freigegeben bei Fehler
return -1;
}
*data = shared_counter;
pthread_mutex_unlock(&lock);
return 0;
}
// VERWUNDBAR: Doppelsperre (nicht-reentrantfähig)
void outer_function_vulnerable() {
pthread_mutex_lock(&lock);
inner_function_vulnerable(); // Versucht auch zu sperren
pthread_mutex_unlock(&lock);
}
void inner_function_vulnerable() {
// VERWUNDBAR: Deadlock - Sperre bereits gehalten
pthread_mutex_lock(&lock);
do_work();
pthread_mutex_unlock(&lock);
}
// VERWUNDBAR: Sperrreihenfolgeverletzung verursacht Deadlock
pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER;
void* thread1_vulnerable(void* arg) {
pthread_mutex_lock(&lock_a);
sleep(1); // Erhöht Chance für Deadlock
pthread_mutex_lock(&lock_b); // Wartet auf lock_b
do_work();
pthread_mutex_unlock(&lock_b);
pthread_mutex_unlock(&lock_a);
return NULL;
}
void* thread2_vulnerable(void* arg) {
pthread_mutex_lock(&lock_b); // Bekommt lock_b
sleep(1);
pthread_mutex_lock(&lock_a); // DEADLOCK: Wartet auf lock_a
do_work();
pthread_mutex_unlock(&lock_a);
pthread_mutex_unlock(&lock_b);
return NULL;
}
// VERWUNDBAR: Sperre während blockierender Operation gehalten
void blocking_with_lock_vulnerable() {
pthread_mutex_lock(&lock);
read(socket_fd, buffer, size); // Blockiert während Sperre gehalten!
pthread_mutex_unlock(&lock);
}
# VERWUNDBAR: Python unsachgemäßes Sperren
import threading
counter = 0
lock = threading.Lock()
# VERWUNDBAR: Keine Sperrung
def increment_vulnerable():
global counter
# VERWUNDBAR: Race Condition
counter += 1
# VERWUNDBAR: Sperre nicht freigegeben bei Exception
def process_vulnerable(data):
lock.acquire()
if not data:
# VERWUNDBAR: Sperre nicht freigegeben
raise ValueError("Keine Daten")
result = process(data)
lock.release()
return result
# VERWUNDBAR: Doppelter Erwerb (nicht-reentrantfähig)
def outer_vulnerable():
lock.acquire()
inner_vulnerable() # VERWUNDBAR: Deadlock
lock.release()
def inner_vulnerable():
lock.acquire() # Deadlock - dieselbe Sperre bereits gehalten
do_work()
lock.release()
# VERWUNDBAR: Sperrreihenfolge
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread1_vulnerable():
with lock_a:
time.sleep(0.1)
with lock_b: # Möglicher Deadlock
work()
def thread2_vulnerable():
with lock_b: # Andere Reihenfolge!
time.sleep(0.1)
with lock_a: # DEADLOCK
work()
# VERWUNDBAR: Falscher Sperrbereich
class VulnerableCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment_and_get(self):
with self.lock:
self.value += 1
# VERWUNDBAR: Lesen außerhalb der Sperre
return self.value # Race Condition
// VERWUNDBAR: Java unsachgemäßes Sperren
public class VulnerableLocking {
private int counter = 0;
private final Object lock = new Object();
// VERWUNDBAR: Keine Synchronisierung
public void incrementVulnerable() {
counter++; // Race Condition
}
// VERWUNDBAR: Sperre nicht freigegeben bei Exception
public void processVulnerable(Object data) {
synchronized(lock) {
if (data == null) {
throw new IllegalArgumentException();
// Sperre durch synchronized-Block freigegeben, ABER...
}
process(data);
}
}
// VERWUNDBAR: Doppelsperre (explizite Sperren)
private final ReentrantLock reentrantLock = new ReentrantLock();
// Hinweis: ReentrantLock handhabt das tatsächlich, aber konzeptionell:
// Nicht-reentrantfähige Sperre würde hier deadlocken
// VERWUNDBAR: Sperrreihenfolgeverletzung
private final Object lockA = new Object();
private final Object lockB = new Object();
public void method1() {
synchronized(lockA) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lockB) { // Wartet auf lockB
work();
}
}
}
public void method2() {
synchronized(lockB) { // Andere Reihenfolge
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lockA) { // DEADLOCK
work();
}
}
}
// VERWUNDBAR: Grobkörniges Sperren
private List<User> users = new ArrayList<>();
private List<Order> orders = new ArrayList<>();
public synchronized void updateUser(User u) {
// Hält Sperre sogar für unabhängige Daten
users.add(u);
}
public synchronized void updateOrder(Order o) {
// Dieselbe Sperre, aber andere Daten
orders.add(o); // Unnötige Konkurrenz
}
}
// VERWUNDBAR: JavaScript unsachgemäßes Sperren (async Kontext)
class VulnerableLocking {
constructor() {
this.data = {};
this.processing = false;
}
// VERWUNDBAR: Kein ordnungsgemäßes Sperren für async Operationen
async updateData(key, value) {
// VERWUNDBAR: Check-then-act Race Condition
if (!this.processing) {
this.processing = true;
// Async Lücke - ein anderer Aufruf könnte hier eintreten
await someAsyncOperation();
this.data[key] = value;
this.processing = false;
}
}
// VERWUNDBAR: Sperre nicht freigegeben bei Fehler
async processWithLock() {
this.locked = true;
const result = await fetchData(); // Könnte werfen
// VERWUNDBAR: Wenn fetchData wirft, bleibt locked true
this.locked = false;
return result;
}
// VERWUNDBAR: Kein Warten auf Sperre
async accessShared() {
if (this.locked) {
return null; // Scheitert einfach statt zu warten
}
this.locked = true;
// ... Arbeit ...
this.locked = false;
}
}
Lösungscode
// SICHER: Ordnungsgemäßes Sperren in C
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;
// SICHER: Ordnungsgemäßes Sperren
void increment_safe() {
pthread_mutex_lock(&lock);
shared_counter++;
pthread_mutex_unlock(&lock);
}
// SICHER: Sperre auf allen Pfaden freigegeben
int process_data_safe(int* data) {
int result = 0;
pthread_mutex_lock(&lock);
if (data == NULL) {
result = -1;
goto cleanup;
}
*data = shared_counter;
cleanup:
pthread_mutex_unlock(&lock);
return result;
}
// SICHER: Rekursiven Mutex für Reentrantfähigkeit verwenden
pthread_mutex_t recursive_lock;
void init_recursive_lock() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&recursive_lock, &attr);
pthread_mutexattr_destroy(&attr);
}
void outer_function_safe() {
pthread_mutex_lock(&recursive_lock);
inner_function_safe(); // Sicher - rekursiver Mutex
pthread_mutex_unlock(&recursive_lock);
}
void inner_function_safe() {
pthread_mutex_lock(&recursive_lock); // OK mit rekursivem
do_work();
pthread_mutex_unlock(&recursive_lock);
}
// SICHER: Konsistente Sperrreihenfolge
pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER;
// Immer in Reihenfolge erwerben: lock_a, dann lock_b
void* thread1_safe(void* arg) {
pthread_mutex_lock(&lock_a); // Erste
pthread_mutex_lock(&lock_b); // Zweite
do_work();
pthread_mutex_unlock(&lock_b);
pthread_mutex_unlock(&lock_a);
return NULL;
}
void* thread2_safe(void* arg) {
pthread_mutex_lock(&lock_a); // Dieselbe Reihenfolge!
pthread_mutex_lock(&lock_b);
do_work();
pthread_mutex_unlock(&lock_b);
pthread_mutex_unlock(&lock_a);
return NULL;
}
// SICHER: Minimaler Sperrbereich
void process_with_minimal_lock() {
char buffer[1024];
// Lesen ohne Sperre (wenn Socket thread-lokal ist)
read(socket_fd, buffer, sizeof(buffer));
// Nur für gemeinsamen Zustandszugriff sperren
pthread_mutex_lock(&lock);
update_shared_state(buffer);
pthread_mutex_unlock(&lock);
}
# SICHER: Python ordnungsgemäßes Sperren
import threading
from contextlib import contextmanager
counter = 0
lock = threading.Lock()
# SICHER: with-Anweisung verwenden
def increment_safe():
global counter
with lock:
counter += 1
# SICHER: Sperre bei Exception freigegeben (Context-Manager)
def process_safe(data):
with lock:
if not data:
raise ValueError("Keine Daten") # Sperre automatisch freigegeben
return process(data)
# SICHER: RLock für reentrantfähiges Sperren verwenden
rlock = threading.RLock()
def outer_safe():
with rlock:
inner_safe() # Sicher mit RLock
def inner_safe():
with rlock: # RLock erlaubt reentrantfähigen Erwerb
do_work()
# SICHER: Konsistente Sperrreihenfolge
lock_a = threading.Lock()
lock_b = threading.Lock()
def acquire_both():
"""Immer in alphabetischer Reihenfolge erwerben"""
with lock_a:
with lock_b:
return work()
def thread1_safe():
acquire_both()
def thread2_safe():
acquire_both() # Dieselbe Reihenfolge
# SICHER: Sperrreihenfolge mit Context-Manager
@contextmanager
def ordered_locks(*locks):
"""Mehrere Sperren in konsistenter Reihenfolge erwerben"""
sorted_locks = sorted(locks, key=id)
try:
for lock in sorted_locks:
lock.acquire()
yield
finally:
for lock in reversed(sorted_locks):
lock.release()
def safe_multi_lock():
with ordered_locks(lock_a, lock_b):
# Reihenfolge durch id bestimmt, immer konsistent
work()
# SICHER: Korrekter Sperrbereich
class SafeCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment_and_get(self):
with self.lock:
self.value += 1
return self.value # Lesen innerhalb der Sperre
// SICHER: Java ordnungsgemäßes Sperren
public class SafeLocking {
private int counter = 0;
private final Object lock = new Object();
// SICHER: Synchronisierte Methode
public synchronized void incrementSafe() {
counter++;
}
// SICHER: Sperre mit try-finally
private final ReentrantLock reentrantLock = new ReentrantLock();
public void processWithLock(Object data) {
reentrantLock.lock();
try {
if (data == null) {
throw new IllegalArgumentException();
}
process(data);
} finally {
// SICHER: Immer freigegeben
reentrantLock.unlock();
}
}
// SICHER: Konsistente Sperrreihenfolge
private final Object lockA = new Object();
private final Object lockB = new Object();
private void acquireBothLocks() {
// Immer dieselbe Reihenfolge basierend auf Identity-Hash
Object first = System.identityHashCode(lockA) < System.identityHashCode(lockB)
? lockA : lockB;
Object second = first == lockA ? lockB : lockA;
synchronized(first) {
synchronized(second) {
work();
}
}
}
// SICHER: Feinkörniges Sperren
private final List<User> users = new ArrayList<>();
private final List<Order> orders = new ArrayList<>();
private final Object usersLock = new Object();
private final Object ordersLock = new Object();
public void updateUser(User u) {
synchronized(usersLock) {
users.add(u);
}
}
public void updateOrder(Order o) {
synchronized(ordersLock) {
// Andere Sperre - keine Konkurrenz
orders.add(o);
}
}
// SICHER: Nebenläufige Collections verwenden
private final ConcurrentHashMap<String, User> userMap = new ConcurrentHashMap<>();
public void addUser(String id, User user) {
// Thread-sicher ohne explizites Sperren
userMap.put(id, user);
}
// SICHER: Read-Write-Lock für leseintensive Workloads
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private List<String> cache = new ArrayList<>();
public List<String> readCache() {
rwLock.readLock().lock();
try {
return new ArrayList<>(cache);
} finally {
rwLock.readLock().unlock();
}
}
public void updateCache(List<String> newData) {
rwLock.writeLock().lock();
try {
cache = new ArrayList<>(newData);
} finally {
rwLock.writeLock().unlock();
}
}
}
// SICHER: JavaScript ordnungsgemäßes async Sperren
const { Mutex, withTimeout } = require('async-mutex');
class SafeLocking {
constructor() {
this.data = {};
this.mutex = new Mutex();
}
// SICHER: Mutex für async Operationen verwenden
async updateData(key, value) {
const release = await this.mutex.acquire();
try {
await someAsyncOperation();
this.data[key] = value;
} finally {
// SICHER: Immer freigegeben
release();
}
}
// SICHER: runExclusive-Helfer verwenden
async processWithLock() {
return await this.mutex.runExclusive(async () => {
const result = await fetchData();
// Sperre automatisch freigegeben auch bei Fehler
return result;
});
}
// SICHER: Timeout um Deadlock zu verhindern
async accessWithTimeout() {
try {
return await withTimeout(this.mutex, 5000).runExclusive(async () => {
return await slowOperation();
});
} catch (e) {
if (e.message === 'timeout') {
console.log('Sperrerwerb Timeout');
return null;
}
throw e;
}
}
}
// SICHER: Semaphore für begrenzte Nebenläufigkeit
const { Semaphore } = require('async-mutex');
class ConnectionPool {
constructor(maxConnections) {
this.semaphore = new Semaphore(maxConnections);
this.connections = [];
}
async withConnection(fn) {
const [value, release] = await this.semaphore.acquire();
try {
const conn = await this.getConnection();
return await fn(conn);
} finally {
release();
}
}
}
Ausgenutzt in der Praxis
Deadlock-Angriffe
Auslösen von Sperrreihenfolgeverletzungen um DoS zu verursachen.
Race-Condition-Exploits
Ausnutzung fehlender Sperren für Privilegieneskalation.
Prioritätsinversion
Echtzeit-Systemausfälle durch unsachgemäßes Sperren.
Werkzeuge zum Testen/Ausnutzen
-
Thread-Sanitizer (TSan, Helgrind).
-
Deadlock-Detektoren.
-
Sperranalysewerkzeuge.
CVE-Beispiele
-
CVE-2016-9576: Kernel-Sperrreihenfolge-Deadlock.
-
CVE-2019-12379: Race Condition durch fehlende Sperre.
Referenzen
-
MITRE. "CWE-667: Improper Locking." https://cwe.mitre.org/data/definitions/667.html
-
"The Art of Multiprocessor Programming" - Sperrreihenfolge.