Falsche Synchronisation
Beschreibung
Falsche Synchronisation ist eine Nebenläufigkeits-Schwachstelle, bei der Software versucht, den Zugriff auf eine gemeinsam genutzte Ressource zu synchronisieren, dies aber falsch macht und die Ressource nicht ordnungsgemäß vor Problemen beim nebenläufigen Zugriff schützt. Im Gegensatz zur fehlenden Synchronisation (CWE-820), wo kein Schutz versucht wird, bedeutet falsche Synchronisation, dass der Entwickler versucht hat, Thread-Sicherheit zu implementieren, aber Fehler bei der Implementierung gemacht hat. Häufige Fehler sind die Verwendung des falschen Lock-Objekts, das Nicht-Halten von Locks für die gesamte Dauer zusammengesetzter Operationen, die Verwendung nicht-atomarer Check-then-Act-Sequenzen oder das zu frühe Freigeben von Locks, bevor die geschützte Operation abgeschlossen ist.
Risiko
Falsche Synchronisation erzeugt ein falsches Sicherheitsgefühl - Entwickler glauben, der Code sei thread-sicher, obwohl er es nicht ist. Die resultierenden Race Conditions können schwerer zu identifizieren sein, weil der Synchronisationscode suggeriert, dass das Problem berücksichtigt wurde. Angreifer, die Nebenläufigkeitsfehler verstehen, können diese Mängel ausnutzen, um Daten zu beschädigen, Sicherheitsprüfungen zu umgehen oder den Programmzustand zu manipulieren. Die intermittierende Natur von Race Conditions macht sie schwer zu reproduzieren und zu debuggen, was es ermöglicht, dass Schwachstellen unentdeckt in der Produktion bestehen bleiben. Sicherheitskritischer Code mit falscher Synchronisation kann Privilegieneskalation, Authentifizierungsumgehung oder unbefugten Datenzugriff durch sorgfältig getimte Angriffe ermöglichen.
Lösung
Stellen Sie sicher, dass die Synchronisation den gesamten kritischen Abschnitt abdeckt, nicht nur einzelne Operationen. Schützen Sie zusammengesetzte Operationen (Check-then-Act) mit einem einzigen durchgehend gehaltenen Lock. Verwenden Sie dasselbe Lock-Objekt konsistent für alle Zugriffe auf eine bestimmte gemeinsam genutzte Ressource. Überprüfen Sie, dass Lock-Erfassung und -Freigabe ordnungsgemäß gepaart sind, auch in Exception-Handlern. Erwägen Sie die Verwendung von Synchronisationsabstraktionen höherer Ebene, die schwerer falsch zu verwenden sind. Setzen Sie statische Analysetools ein, die speziell zur Erkennung von Synchronisationsfehlern entwickelt wurden. Überprüfen Sie den Code auf Muster wie Double-Checked Locking und stellen Sie sicher, dass sie für die Sprache und Plattform korrekt implementiert sind. Testen Sie mit Nebenläufigkeits-Testtools und Stresstests, die Race Conditions ausüben.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Falsche Synchronisation ermöglicht nebenläufige Modifikationen, die gemeinsame Daten beschädigen. |
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Race Conditions können sensible Daten oder Zwischenzustande unbefugten Lesern preisgeben. |
| Sonstiges | Bereich: Sonstiges Ausführungslogik ändern - Timing-basierte Ausnutzung kann den Kontrollfluss durch Synchronisationslücken manipulieren. |
Beispielcode
Anfälliger Code
// Anfällig: Synchronisation auf falschem Objekt
public class VulnerableWrongLock {
private List<String> items = new ArrayList<>();
private Object lock1 = new Object();
private Object lock2 = new Object();
public void addItem(String item) {
synchronized (lock1) { // Verwendet lock1
items.add(item);
}
}
public String getItem(int index) {
synchronized (lock2) { // Anfällig: Verwendet anderen Lock!
return items.get(index);
}
}
// Kein tatsächlicher Schutz - verschiedene Locks schließen sich nicht gegenseitig aus
}
// Anfällig: Check-then-Act nicht atomar
public class VulnerableCheckThenAct {
private Map<String, Object> cache = new HashMap<>();
private Object lock = new Object();
public Object getOrCreate(String key) {
// Anfällig: Lock wird zwischen Prüfung und Put freigegeben
synchronized (lock) {
if (cache.containsKey(key)) {
return cache.get(key);
}
}
// Lücke hier - ein anderer Thread kann einfügen
Object newValue = createExpensiveObject(key);
synchronized (lock) {
cache.put(key, newValue); // Kann Wert eines anderen Threads überschreiben
}
return newValue;
}
}
# Anfällig: Lock wird nicht während der gesamten zusammengesetzten Operation gehalten
import threading
class VulnerableBalance:
def __init__(self):
self.balance = 0
self.lock = threading.Lock()
def transfer(self, amount, target):
# Anfällig: Separate Locks für Prüfung und Modifikation
with self.lock:
if self.balance >= amount:
current = self.balance
# Lücke - ein anderer Thread kann balance hier modifizieren!
with self.lock:
self.balance = current - amount
with target.lock:
target.balance += amount
Korrigierter Code
// Korrigiert: Dasselbe Lock für alle Zugriffe verwenden
public class FixedSameLock {
private List<String> items = new ArrayList<>();
private final Object lock = new Object(); // Einzelnes, finales Lock
public void addItem(String item) {
synchronized (lock) {
items.add(item);
}
}
public String getItem(int index) {
synchronized (lock) { // Dasselbe Lock
return items.get(index);
}
}
}
// Korrigiert: Atomares Check-then-Act
public class FixedCheckThenAct {
private Map<String, Object> cache = new HashMap<>();
private final Object lock = new Object();
public Object getOrCreate(String key) {
synchronized (lock) {
// Korrigiert: Gesamte Operation unter einem Lock
if (cache.containsKey(key)) {
return cache.get(key);
}
Object newValue = createExpensiveObject(key);
cache.put(key, newValue);
return newValue;
}
}
}
// Oder ConcurrentHashMap mit computeIfAbsent verwenden
import java.util.concurrent.ConcurrentHashMap;
public class BetterCheckThenAct {
private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public Object getOrCreate(String key) {
return cache.computeIfAbsent(key, this::createExpensiveObject);
}
}
# Korrigiert: Lock für gesamte zusammengesetzte Operation halten
import threading
class FixedBalance:
def __init__(self):
self.balance = 0
self.lock = threading.Lock()
def transfer(self, amount, target):
# Korrigiert: Geordnetes Sperren verwenden, um Deadlock zu verhindern
# Immer in konsistenter Reihenfolge sperren (nach id)
first, second = (self, target) if id(self) < id(target) else (target, self)
with first.lock:
with second.lock:
# Korrigiert: Gesamte Operation ist atomar
if self.balance >= amount:
self.balance -= amount
target.balance += amount
return True
return False
Verwandte CWEs
- CWE-662: Unzureichende Synchronisation (Eltern)
- CWE-820: Fehlende Synchronisation (Geschwister)
- CWE-572: Aufruf von Thread run() anstatt start() (Kind)
- CWE-574: EJB Bad Practices: Verwendung von Synchronisationsprimitiven (Kind)
- CWE-362: Nebenläufige Ausführung mit gemeinsamer Ressource ohne ordnungsgemäße Synchronisation (verwandt)
Referenzen
- MITRE Corporation. "CWE-821: Incorrect Synchronization." https://cwe.mitre.org/data/definitions/821.html
- CERT Java Secure Coding. "LCK00-J. Use private final lock objects to synchronize classes that may interact with untrusted code."
- Götz, Brian. "Java Concurrency in Practice." Addison-Wesley, 2006.