Use of Singleton Pattern Without Synchronization in a Multithreaded Context
Description
Use of Singleton Pattern Without Synchronization in a Multithreaded Context is a vulnerability where a product implements the singleton pattern when creating resources in multithreaded environments without proper thread-safety mechanisms. The classic singleton implementation with a null check and instance creation is not atomic, allowing multiple threads to pass the null check simultaneously and each create separate instances. This violates the singleton contract, can cause data inconsistency when each instance maintains different state, and may lead to resource leaks from duplicate initialization.
Risk
Thread-unsafe singletons create subtle concurrency bugs that are difficult to reproduce and debug. When multiple instances are created, state changes in one instance may not be visible to code using a different instance, causing inconsistent behavior. In security contexts, this can lead to bypassed authentication checks, duplicated access tokens, or race conditions in authorization logic. Resource management singletons may cause connection leaks or file handle exhaustion. Configuration singletons may return stale or inconsistent settings. The double-checked locking pattern, often used as a "fix," is itself broken in Java prior to version 5 and C++ prior to C++11 due to memory model issues.
Solution
Use language-specific thread-safe singleton implementations. In Java, use eager initialization with static final, enum-based singletons, or synchronized getInstance() methods. For lazy initialization in Java 5+, use the volatile keyword with double-checked locking. Consider using initialization-on-demand holder idiom. In C++11 and later, use static local variables which are guaranteed thread-safe. Alternatively, use dependency injection frameworks that manage singleton scope. Avoid storing mutable user-specific data in singletons. When thread-local storage is needed, use ThreadLocal instead of singletons.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Multiple singleton instances can be created, each maintaining different state, leading to data inconsistency when code operates on different instances. |
| Other | Scope: Other Quality Degradation - The singleton contract is violated, leading to unpredictable behavior that depends on thread timing and is difficult to reproduce or debug. |
Example Code
Vulnerable Code
// Vulnerable: Classic lazy singleton without synchronization
public class VulnerableSingleton {
private static VulnerableSingleton instance;
private int counter = 0;
private VulnerableSingleton() {
// Initialize singleton
System.out.println("Singleton created");
}
// Vulnerable: Not thread-safe
public static VulnerableSingleton getInstance() {
if (instance == null) { // Thread A checks, sees null
// Thread B also checks, also sees null
instance = new VulnerableSingleton(); // Both create instances!
}
return instance; // Different threads may get different instances
}
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
// Demonstration of the race condition:
// Thread A: checks instance == null, true
// Thread A: begins creating new instance
// Thread B: checks instance == null, still true (not assigned yet)
// Thread B: creates another new instance
// Thread A: assigns first instance to variable
// Thread B: overwrites with second instance
// Result: two instances created, first instance orphaned
// Vulnerable: Double-checked locking without volatile (pre-Java 5 style)
public class VulnerableDoubleChecked {
private static VulnerableDoubleChecked instance; // Missing volatile!
public static VulnerableDoubleChecked getInstance() {
if (instance == null) {
synchronized (VulnerableDoubleChecked.class) {
if (instance == null) {
instance = new VulnerableDoubleChecked();
// Vulnerable: Assignment may be reordered
// Another thread might see non-null reference
// to partially constructed object!
}
}
}
return instance;
}
}
// Vulnerable: C++ singleton without synchronization
class VulnerableSingleton {
private:
static VulnerableSingleton* instance;
int data;
VulnerableSingleton() : data(0) {}
public:
// Vulnerable: Race condition in getInstance
static VulnerableSingleton* getInstance() {
if (instance == nullptr) { // Not thread-safe!
instance = new VulnerableSingleton();
}
return instance;
}
void setData(int d) { data = d; }
int getData() { return data; }
};
VulnerableSingleton* VulnerableSingleton::instance = nullptr;
// Vulnerable: Double-checked locking (broken pre-C++11)
class VulnerableDoubleChecked {
private:
static VulnerableDoubleChecked* instance;
static pthread_mutex_t mutex;
public:
static VulnerableDoubleChecked* getInstance() {
if (instance == nullptr) {
pthread_mutex_lock(&mutex);
if (instance == nullptr) {
instance = new VulnerableDoubleChecked();
// Vulnerable: Memory reordering can cause
// another thread to see non-null pointer
// to partially constructed object
}
pthread_mutex_unlock(&mutex);
}
return instance;
}
};
# Vulnerable: Python singleton without locking
class VulnerableSingleton:
_instance = None
def __new__(cls):
if cls._instance is None: # Race condition!
# Thread switch could happen here
cls._instance = super().__new__(cls)
# Another thread might also create an instance
return cls._instance
# Vulnerable: Module-level singleton without thread safety
class VulnerableConfig:
_instance = None
_config = {}
@classmethod
def get_instance(cls):
if cls._instance is None:
# Vulnerable: Multiple threads can reach here
cls._instance = cls()
cls._instance._load_config()
return cls._instance
def _load_config(self):
# Expensive initialization that shouldn't happen twice
self._config = load_from_file()
// Vulnerable: Node.js singleton in async environment
class VulnerableSingleton {
static instance = null;
static async getInstance() {
if (VulnerableSingleton.instance === null) {
// Vulnerable: Multiple concurrent calls can pass this check
VulnerableSingleton.instance = new VulnerableSingleton();
await VulnerableSingleton.instance.initialize();
// initialize() is async, race condition during await
}
return VulnerableSingleton.instance;
}
async initialize() {
// Expensive async initialization
this.data = await loadConfiguration();
}
}
Fixed Code
// Fixed: Thread-safe singleton implementations in Java
// Option 1: Eager initialization (simplest, always thread-safe)
public class EagerSingleton {
// Fixed: Static final guarantees thread-safe initialization
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
// Option 2: Enum singleton (recommended by Effective Java)
public enum EnumSingleton {
INSTANCE;
private int counter = 0;
public void increment() { counter++; }
public int getCounter() { return counter; }
}
// Usage: EnumSingleton.INSTANCE.increment();
// Option 3: Double-checked locking with volatile (Java 5+)
public class VolatileSingleton {
// Fixed: volatile ensures visibility and prevents reordering
private static volatile VolatileSingleton instance;
private VolatileSingleton() {}
public static VolatileSingleton getInstance() {
if (instance == null) {
synchronized (VolatileSingleton.class) {
if (instance == null) {
instance = new VolatileSingleton();
}
}
}
return instance;
}
}
// Option 4: Initialization-on-demand holder idiom
public class HolderSingleton {
private HolderSingleton() {}
// Fixed: Static holder class provides lazy, thread-safe initialization
private static class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE; // Class loading is thread-safe
}
}
// Option 5: Synchronized method (simple but potentially slower)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// Fixed: synchronized ensures only one thread at a time
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
// Fixed: C++11 thread-safe singleton
// Option 1: Meyers' Singleton (C++11 guarantees thread-safe)
class MeyersSingleton {
public:
// Fixed: C++11 guarantees static local is initialized thread-safely
static MeyersSingleton& getInstance() {
static MeyersSingleton instance;
return instance;
}
// Delete copy and move constructors
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
MeyersSingleton(MeyersSingleton&&) = delete;
MeyersSingleton& operator=(MeyersSingleton&&) = delete;
private:
MeyersSingleton() {}
};
// Option 2: Double-checked locking with atomic (C++11)
#include <atomic>
#include <mutex>
class AtomicSingleton {
private:
static std::atomic<AtomicSingleton*> instance;
static std::mutex mtx;
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;
}
};
std::atomic<AtomicSingleton*> AtomicSingleton::instance{nullptr};
std::mutex AtomicSingleton::mtx;
// Option 3: call_once (C++11)
#include <memory>
class CallOnceSingleton {
private:
static std::unique_ptr<CallOnceSingleton> instance;
static std::once_flag initFlag;
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;
# Fixed: Python thread-safe singleton implementations
import threading
# Option 1: Thread-safe singleton with lock
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock: # Fixed: Acquire lock
# Double-check inside lock
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Option 2: Using module-level variable (Pythonic approach)
# singleton.py
class _Singleton:
def __init__(self):
self.data = {}
# Fixed: Module-level instance created at import time (thread-safe)
singleton_instance = _Singleton()
# Other modules: from singleton import singleton_instance
# Option 3: Metaclass-based singleton
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.value = 0
# Option 4: Using functools.lru_cache
from functools import lru_cache
class ConfigSingleton:
def __init__(self):
self.config = self._load_config()
def _load_config(self):
return {'key': 'value'}
@lru_cache(maxsize=1)
def get_config_singleton():
"""Fixed: lru_cache ensures single creation."""
return ConfigSingleton()
// Fixed: Node.js thread-safe singleton patterns
// Option 1: Module caching (Node.js caches modules)
// singleton.js
class Singleton {
constructor() {
this.data = {};
}
static instance = null;
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// Fixed: Module exports ensure single instance
module.exports = Singleton.getInstance();
// Option 2: Async singleton with promise
class AsyncSingleton {
static instance = null;
static initializationPromise = null;
static async getInstance() {
if (AsyncSingleton.instance) {
return AsyncSingleton.instance;
}
// Fixed: Use promise to prevent duplicate initialization
if (!AsyncSingleton.initializationPromise) {
AsyncSingleton.initializationPromise = (async () => {
const instance = new AsyncSingleton();
await instance.initialize();
AsyncSingleton.instance = instance;
return instance;
})();
}
return AsyncSingleton.initializationPromise;
}
async initialize() {
this.data = await loadConfiguration();
}
}
// Option 3: Frozen singleton
const frozenSingleton = Object.freeze({
data: {},
getValue(key) { return this.data[key]; },
setValue(key, value) { this.data[key] = value; }
});
module.exports = frozenSingleton;
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:
- Concurrency bug research
- Java memory model specifications
- C++ standard committee papers
References
- MITRE Corporation. "CWE-543: Use of Singleton Pattern Without Synchronization in a Multithreaded Context." https://cwe.mitre.org/data/definitions/543.html
- Bloch, Joshua. "Effective Java" - Item 3: Enforce the singleton property.
- CERT Oracle Secure Coding Standard for Java. "LCK10-J. Use a correct form of the double-checked locking idiom."