Singleton-Klasseninstanz-Erstellung ohne ordnungsgemäße Sperrung oder Synchronisierung
Beschreibung
Singleton-Klasseninstanz-Erstellung ohne ordnungsgemäße Sperrung oder Synchronisierung tritt auf, wenn ein Produkt das Singleton-Entwurfsmuster implementiert, aber keine geeigneten Sperr- oder Synchronisierungsmechanismen verwendet, um sicherzustellen, dass die Singleton-Klasse nur einmal instanziiert wird. In Multithreaded-Umgebungen können mehrere Threads gleichzeitig versuchen, die Singleton-Instanz zu erstellen, was dazu führt, dass mehrere Instanzen erstellt werden. Dies verletzt die grundlegende Garantie des Singleton-Musters und kann zu inkonsistentem Zustand, Ressourcenlecks und Race Conditions führen.
Risiko
Fehlende Synchronisierung bei Singleton-Erstellung hat Sicherheitsimplikationen. Mehrere Singleton-Instanzen können inkonsistenten Zustand halten, was zu Sicherheitsumgehungen führt. Race Conditions während der Instanziierung können gemeinsam genutzte Daten korrumpieren. Ressourceninitialisierung kann mehrfach erfolgen, was zu Lecks führt. Sicherheitskritische Singletons (wie Sicherheitsmanager) können mehrere widerspruchende Instanzen haben. Konfigurations-Singletons können unterschiedliche Konfigurationen in verschiedenen Teilen der Anwendung haben. Das unvorhersehbare Verhalten in gleichzeitigen Szenarien kann ausgenutzt werden. Deadlock- oder Livelock-Bedingungen können bei falscher Synchronisierung auftreten.
Lösung
Verwenden Sie ordnungsgemäße Synchronisierungsmechanismen bei der Implementierung von Singletons. In Java verwenden Sie statische Initialisierung (Eager Singleton), synchronisierte Methoden oder Double-Checked Locking mit volatile. In C++ verwenden Sie lokale statische Variablen (garantiert Thread-sicher seit C++11) oder ordnungsgemäße Mutex-Sperrung. Erwägen Sie die Verwendung von Framework-bereitgestellten Singleton-Implementierungen. Verwenden Sie das Enum-Singleton-Muster in Java für Einfachheit und Thread-Sicherheit. Wenden Sie statische Analysetools an, um unsynchronisierte Singletons zu erkennen. Testen Sie Singleton-Implementierungen unter gleichzeitiger Last. Dokumentieren Sie Thread-Sicherheitsgarantien für Singleton-Klassen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Unerwarteter Zustand - Mehrere Instanzen können unterschiedlichen Zustand halten und Inkonsistenzen verursachen. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Ressourcen können mehrfach allokiert werden. |
| Andere | Bereich: Ändere Reduzierte Zuverlässigkeit - Race Conditions machen Verhalten unvorhersehbar. |
Beispielcode
Anfälliger Code
// Anfällig: Klassischer unsynchronisierter Singleton
public class VulnerableSingleton {
private static VulnerableSingleton instance;
private Configuration config;
private VulnerableSingleton() {
// Teure Initialisierung
config = loadConfiguration();
}
// Anfällig: Keine Synchronisierung!
public static VulnerableSingleton getInstance() {
if (instance == null) { // Thread A prüft: null
// Thread B prüft auch: null (Race Condition!)
instance = new VulnerableSingleton();
// Beide Threads erstellen Instanzen!
}
return instance;
}
// Ergebnis: Mehrere Instanzen mit potenziell unterschiedlichen Konfigurationen
}
// Anfällig: Fehlerhaftes Double-Checked Locking (vor Java 5)
public class VulnerableDoubleChecked {
private static VulnerableDoubleChecked instance; // Nicht volatile!
private VulnerableDoubleChecked() {
initializeResources();
}
public static VulnerableDoubleChecked getInstance() {
if (instance == null) {
synchronized (VulnerableDoubleChecked.class) {
if (instance == null) {
// Anfällig: Ohne volatile kann ein anderer Thread
// teilweise konstruiertes Objekt durch Neuordnung sehen
instance = new VulnerableDoubleChecked();
}
}
}
return instance;
}
}
// Anfällig: Sicherheitsmanager-Singleton ohne Synchronisierung
public class VulnerableSecurityManager {
private static VulnerableSecurityManager instance;
private Set<String> permissions;
private boolean initialized = false;
private VulnerableSecurityManager() {
permissions = new HashSet<>();
loadPermissions();
initialized = true;
}
public static VulnerableSecurityManager getInstance() {
if (instance == null) {
instance = new VulnerableSecurityManager();
}
return instance;
}
public boolean hasPermission(String permission) {
// Anfällig: Kann auf nicht-initialisierte Berechtigungen zugreifen
// wenn ein anderer Thread teilweise konstruierte Instanz bekommt
return permissions.contains(permission);
}
}
# Anfällig: Python-Singleton ohne Thread-Sicherheit
class VulnerableSingleton:
_instance = None
def __new__(cls):
# Anfällig: Race Condition zwischen Prüfung und Zuweisung
if cls._instance is None:
# Thread-Kontextwechsel hier könnte mehrere Instanzen verursachen
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if not self._initialized:
# Anfällig: Initialisierung hat auch Race Condition
self.config = self._load_config()
self.connections = []
self._initialized = True
# Anfällig: Modul-ebenen Singleton-Muster mit Lazy Init
class VulnerableConnectionPool:
_instance = None
@classmethod
def get_instance(cls):
# Anfällig: Kein Lock-Schutz
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
self.pool = []
self._create_connections()
def _create_connections(self):
# Jede Instanz erstellt eigene Verbindungen
for _ in range(10):
self.pool.append(create_connection())
# Mehrere Instanzen = Verbindungsleck!
// Anfällig: C++-Singleton ohne Synchronisierung
class VulnerableSingleton {
private:
static VulnerableSingleton* instance;
Config* config;
VulnerableSingleton() {
config = new Config();
config->load();
}
public:
// Anfällig: Klassische Race Condition
static VulnerableSingleton* getInstance() {
if (instance == nullptr) { // Race Condition!
instance = new VulnerableSingleton();
}
return instance;
}
~VulnerableSingleton() {
delete config;
}
};
VulnerableSingleton* VulnerableSingleton::instance = nullptr;
// Anfällig: Fehlerhaftes Double-Checked Locking in C++03
class VulnerableDCL {
private:
static VulnerableDCL* instance;
static std::mutex mtx;
public:
static VulnerableDCL* getInstance() {
if (instance == nullptr) { // Erste Prüfung ohne Lock
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
// Anfällig in C++03: Keine Memory Barrier
// Ein anderer Thread kann nicht-initialisiertes Objekt sehen
instance = new VulnerableDCL();
}
}
return instance;
}
};
Korrigierter Code
// Korrigiert: Mehrere korrekte Ansätze in Java
// Ansatz 1: Eager Initialization (einfachster)
public class EagerSingleton {
// Beim Laden der Klasse erstellt - Thread-sicher
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
initializeResources();
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
// Ansatz 2: Initialization-on-demand Holder (Lazy + Thread-sicher)
public class HolderSingleton {
private HolderSingleton() {
initializeResources();
}
// Innere Klasse wird nicht geladen bis getInstance() aufgerufen
private static class Holder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE; // Klassenladen ist Thread-sicher
}
}
// Ansatz 3: Enum-Singleton (empfohlen von Joshua Bloch)
public enum EnumSingleton {
INSTANCE;
private final Configuration config;
EnumSingleton() {
config = loadConfiguration();
}
public Configuration getConfig() {
return config;
}
}
// Ansatz 4: Korrektes Double-Checked Locking (Java 5+)
public class FixedDoubleChecked {
// volatile ist ERFORDERLICH für korrektes Verhalten
private static volatile FixedDoubleChecked instance;
private FixedDoubleChecked() {
initializeResources();
}
public static FixedDoubleChecked getInstance() {
// Lokale Variable verbessert Leistung
FixedDoubleChecked localRef = instance;
if (localRef == null) {
synchronized (FixedDoubleChecked.class) {
localRef = instance;
if (localRef == null) {
instance = localRef = new FixedDoubleChecked();
}
}
}
return localRef;
}
}
// Ansatz 5: Thread-sicherer Sicherheitsmanager
public class FixedSecurityManager {
private static volatile FixedSecurityManager instance;
private final Set<String> permissions;
private FixedSecurityManager() {
Set<String> perms = new HashSet<>();
loadPermissions(perms);
// Unveränderliche Ansicht für Thread-Sicherheit
permissions = Collections.unmodifiableSet(perms);
}
public static FixedSecurityManager getInstance() {
if (instance == null) {
synchronized (FixedSecurityManager.class) {
if (instance == null) {
instance = new FixedSecurityManager();
}
}
}
return instance;
}
public boolean hasPermission(String permission) {
return permissions.contains(permission);
}
}
# Korrigiert: Thread-sichere Singleton-Muster in Python
import threading
from functools import lru_cache
# Ansatz 1: Modul-ebene Instanz (Python-Module sind Singletons)
# my_singleton.py
class _Singleton:
def __init__(self):
self.config = self._load_config()
def _load_config(self):
return {"setting": "value"}
# Modul-ebene Instanz - einmal beim Import erstellt
singleton_instance = _Singleton()
# Ansatz 2: Thread-sicherer Lazy Singleton mit Lock
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock: # Lock erwerben
# Double-Check innerhalb des Locks
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# Re-Initialisierung verhindern
if self._initialized:
return
with self._lock:
if not self._initialized:
self.config = self._load_config()
self._initialized = True
# Ansatz 3: Mit Metaklasse
class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class MySingleton(metaclass=SingletonMeta):
def __init__(self):
self.config = {}
# Ansatz 4: Mit lru_cache Dekorator
class ConfigManager:
def __init__(self):
self.settings = self._load_settings()
def _load_settings(self):
return {"db": "postgres://..."}
@lru_cache(maxsize=1)
def get_config_manager() -> ConfigManager:
"""Thread-sicherer Singleton via lru_cache."""
return ConfigManager()
# Ansatz 5: Thread-sicherer Connection Pool
class FixedConnectionPool:
_instance = None
_lock = threading.Lock()
@classmethod
def get_instance(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
self._pool_lock = threading.Lock()
self.pool = []
self._create_connections()
def _create_connections(self):
for _ in range(10):
self.pool.append(create_connection())
def get_connection(self):
with self._pool_lock:
if self.pool:
return self.pool.pop()
return create_connection() # Fallback
// Korrigiert: Thread-sichere Singleton-Muster in C++
// Ansatz 1: Meyers' Singleton (C++11 und später - garantiert Thread-sicher)
class MeyersSingleton {
public:
static MeyersSingleton& getInstance() {
// C++11 garantiert, dass dies Thread-sicher ist
static MeyersSingleton instance;
return instance;
}
// Kopieren und Verschieben löschen
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
private:
MeyersSingleton() {
config = std::make_unique<Config>();
config->load();
}
std::unique_ptr<Config> config;
};
// Ansatz 2: Double-Checked Locking mit Atomic (C++11)
class AtomicSingleton {
private:
static std::atomic<AtomicSingleton*> instance;
static std::mutex mtx;
Config* config;
AtomicSingleton() {
config = new Config();
config->load();
}
public:
static AtomicSingleton* getInstance() {
AtomicSingleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new AtomicSingleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
~AtomicSingleton() {
delete config;
}
};
std::atomic<AtomicSingleton*> AtomicSingleton::instance{nullptr};
std::mutex AtomicSingleton::mtx;
// Ansatz 3: call_once (C++11)
class CallOnceSingleton {
private:
static std::unique_ptr<CallOnceSingleton> instance;
static std::once_flag initFlag;
CallOnceSingleton() {
// Initialisieren
}
public:
static CallOnceSingleton& getInstance() {
std::call_once(initFlag, []() {
instance.reset(new CallOnceSingleton());
});
return *instance;
}
};
std::unique_ptr<CallOnceSingleton> CallOnceSingleton::instance;
std::once_flag CallOnceSingleton::initFlag;
// Ansatz 4: Template-Singleton zur Wiederverwendung
template<typename T>
class Singleton {
public:
static T& getInstance() {
static T instance; // Thread-sicher in C++11
return instance;
}
protected:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// Verwendung
class ConfigManager : public Singleton<ConfigManager> {
friend class Singleton<ConfigManager>;
private:
ConfigManager() {
// Konfiguration laden
}
public:
std::string getSetting(const std::string& key);
};
CVE-Beispiele
Race Conditions bei Singleton-Erstellung haben zu verschiedenen Schwachstellen beigetragen, insbesondere bei sicherheitskritischen Komponenten, wo mehrere Instanzen zu inkonsistentem Sicherheitszustand führen könnten.
Verwandte CWEs
- CWE-820: Missing Synchronization (Eltern)
- CWE-662: Improper Synchronization (breitere Kategorie)
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (verwandt)
Referenzen
- MITRE Corporation. "CWE-1096: Singleton Class Instance Creation without Proper Locking or Synchronization." https://cwe.mitre.org/data/definitions/1096.html
- Bloch, Joshua. "Effective Java" - Item 3: Enforce the singleton property with a private constructor or an enum type.
- Meyers, Scott. "Effective C++" - Singleton implementation.