Sensitive Data Storage in Improperly Locked Memory
Description
Sensitive Data Storage in Improperly Locked Memory occurs when an application stores sensitive information (passwords, cryptographic keys, PII) in memory that has not been properly locked to prevent it from being swapped to disk. When memory containing sensitive data is paged out to swap space, the data persists on disk in an unencrypted form, potentially accessible to attackers with disk access or through forensic analysis.
Risk
Sensitive data written to swap can persist long after the application terminates. Attackers with physical access or administrative privileges can read swap files/partitions to extract passwords, keys, or other secrets. The application may appear to handle data securely in memory while actually leaving copies on disk. Cold boot attacks can also recover recently swapped memory contents. This defeats secure memory handling assumptions.
Solution
Use platform-specific memory locking APIs (mlock on Unix, VirtualLock on Windows) to prevent sensitive memory regions from being swapped. Ensure locks succeed before storing secrets. Clear sensitive data immediately after use. Use secure memory allocators that handle locking automatically. On systems where locking isn't available, consider encrypted swap or minimize time sensitive data resides in memory.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Information Disclosure Sensitive data written to swap is accessible to attackers. |
| Security | Scope: Credential Exposure Passwords and keys may be recovered from disk. |
| Compliance | Scope: Regulatory Violation PII in swap may violate data protection requirements. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Sensitive data not locked in memory
void process_password_vulnerable(const char* user_input) {
char password[256];
strcpy(password, user_input);
// Password in unlocked memory - may be swapped to disk!
authenticate(password);
// Even after clearing, swap may retain copy
memset(password, 0, sizeof(password));
}
// VULNERABLE: Crypto key in regular heap memory
void use_crypto_key_vulnerable() {
unsigned char* key = malloc(32);
// Key generated in unlocked memory
generate_key(key);
// May be swapped out during encryption
encrypt_data(key, data, data_len);
memset(key, 0, 32);
free(key);
}
// VULNERABLE: Assuming memory is secure
struct Credentials {
char username[64];
char password[64];
char api_key[128];
};
void store_credentials_vulnerable() {
struct Credentials* creds = malloc(sizeof(struct Credentials));
// Sensitive structure in swappable memory!
load_credentials(creds);
use_credentials(creds);
memset(creds, 0, sizeof(struct Credentials));
free(creds);
}
// VULNERABLE: Long-lived sensitive data
static unsigned char master_key[32]; // Global - exists entire runtime
void init_crypto_vulnerable() {
// Key in .bss segment - definitely swappable
derive_master_key(master_key);
// Key sits in swappable memory for application lifetime
}
// VULNERABLE: C++ with unlocked sensitive data
class VulnerableKeyStore {
std::vector<uint8_t> masterKey;
std::string password;
public:
void setPassword(const std::string& pwd) {
password = pwd; // String in unlocked heap memory
}
void setKey(const uint8_t* key, size_t len) {
masterKey.assign(key, key + len); // Vector in unlocked memory
}
~VulnerableKeyStore() {
// Clear attempt, but swap may have copy
std::fill(password.begin(), password.end(), '\0');
std::fill(masterKey.begin(), masterKey.end(), 0);
}
};
// VULNERABLE: Smart pointer to sensitive data
void process_sensitive_vulnerable() {
auto secret = std::make_unique<char[]>(256);
read_secret(secret.get());
// unique_ptr uses regular heap - swappable
process(secret.get());
}
# VULNERABLE: Python with sensitive data in memory
def process_password_vulnerable(password):
# Python strings are immutable, copies everywhere
# All in regular, swappable memory
hashed = hash_password(password)
# Cannot reliably clear the original string
return hashed
# VULNERABLE: Long-lived secrets
class VulnerableConfig:
def __init__(self):
self.api_key = None
self.db_password = None
def load_secrets(self):
# Secrets loaded into regular Python objects
# May be swapped at any time
self.api_key = read_from_vault("api_key")
self.db_password = read_from_vault("db_password")
Fixed Code
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
// SAFE: Lock memory before storing sensitive data
void process_password_safe(const char* user_input) {
char password[256];
// Lock memory to prevent swapping
if (mlock(password, sizeof(password)) != 0) {
// Handle error - cannot secure memory
handle_error("Failed to lock memory");
return;
}
strcpy(password, user_input);
authenticate(password);
// Clear sensitive data
explicit_bzero(password, sizeof(password));
// Unlock memory
munlock(password, sizeof(password));
}
// SAFE: Secure memory allocation with locking
void* secure_alloc(size_t size) {
void* ptr = malloc(size);
if (ptr == NULL) {
return NULL;
}
// Lock the allocated memory
if (mlock(ptr, size) != 0) {
free(ptr);
return NULL;
}
return ptr;
}
void secure_free(void* ptr, size_t size) {
if (ptr == NULL) return;
// Clear the memory
explicit_bzero(ptr, size);
// Unlock before freeing
munlock(ptr, size);
free(ptr);
}
// SAFE: Crypto key in locked memory
void use_crypto_key_safe() {
unsigned char* key = secure_alloc(32);
if (key == NULL) {
handle_error("Cannot allocate secure memory");
return;
}
generate_key(key);
encrypt_data(key, data, data_len);
secure_free(key, 32);
}
// SAFE: Credentials structure with proper locking
struct SecureCredentials {
char username[64];
char password[64];
char api_key[128];
};
struct SecureCredentials* create_secure_credentials() {
struct SecureCredentials* creds = secure_alloc(sizeof(struct SecureCredentials));
return creds;
}
void destroy_secure_credentials(struct SecureCredentials* creds) {
secure_free(creds, sizeof(struct SecureCredentials));
}
// SAFE: Using mlockall for process-wide locking
void init_secure_process() {
// Lock all current and future memory
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
// May need CAP_IPC_LOCK or root privileges
handle_error("Cannot lock all memory");
}
}
#include <sys/mman.h>
#include <cstring>
#include <memory>
// SAFE: Secure allocator for STL containers
template<typename T>
class SecureAllocator {
public:
using value_type = T;
SecureAllocator() = default;
template<typename U>
SecureAllocator(const SecureAllocator<U>&) {}
T* allocate(size_t n) {
size_t size = n * sizeof(T);
T* ptr = static_cast<T*>(std::malloc(size));
if (ptr && mlock(ptr, size) != 0) {
std::free(ptr);
throw std::bad_alloc();
}
return ptr;
}
void deallocate(T* ptr, size_t n) {
if (ptr) {
size_t size = n * sizeof(T);
explicit_bzero(ptr, size);
munlock(ptr, size);
std::free(ptr);
}
}
};
// SAFE: Secure string type
using SecureString = std::basic_string<char, std::char_traits<char>,
SecureAllocator<char>>;
// SAFE: Secure vector for keys
using SecureBytes = std::vector<uint8_t, SecureAllocator<uint8_t>>;
// SAFE: Key store with locked memory
class SafeKeyStore {
SecureBytes masterKey;
SecureString password;
public:
void setPassword(const std::string& pwd) {
password.assign(pwd.begin(), pwd.end());
}
void setKey(const uint8_t* key, size_t len) {
masterKey.assign(key, key + len);
}
// Destructor automatically clears and unlocks via SecureAllocator
};
// SAFE: RAII wrapper for locked memory
class LockedMemory {
void* ptr;
size_t size;
public:
explicit LockedMemory(size_t sz) : size(sz) {
ptr = std::malloc(size);
if (!ptr) throw std::bad_alloc();
if (mlock(ptr, size) != 0) {
std::free(ptr);
throw std::runtime_error("Failed to lock memory");
}
}
~LockedMemory() {
if (ptr) {
explicit_bzero(ptr, size);
munlock(ptr, size);
std::free(ptr);
}
}
void* get() { return ptr; }
// Prevent copying
LockedMemory(const LockedMemory&) = delete;
LockedMemory& operator=(const LockedMemory&) = delete;
};
# SAFE: Using secure memory libraries
import ctypes
import mmap
import os
class SecureBuffer:
"""Buffer that attempts to prevent swapping"""
def __init__(self, size):
self.size = size
# Allocate page-aligned memory
self.buffer = mmap.mmap(-1, size, mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS)
# Try to lock memory (requires privileges)
try:
import ctypes
libc = ctypes.CDLL("libc.so.6", use_errno=True)
addr = ctypes.c_void_p.from_buffer(self.buffer)
if libc.mlock(addr, size) != 0:
# Log warning but continue
pass
except Exception:
pass
def write(self, data):
self.buffer[:len(data)] = data
def read(self):
return bytes(self.buffer)
def clear(self):
# Overwrite with zeros
self.buffer[:] = b'\x00' * self.size
def __del__(self):
self.clear()
self.buffer.close()
# SAFE: Use specialized libraries
from cryptography.hazmat.primitives import constant_time
import secrets
def process_password_safe(password: bytes) -> bytes:
"""Process password using secure memory practices"""
# Use a library that handles secure memory
# cryptography library uses secure memory internally
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
salt = secrets.token_bytes(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
return kdf.derive(password)
# SAFE: Use keyring for credential storage
import keyring
class SafeCredentialStore:
"""Store credentials in OS keyring (not in Python memory)"""
SERVICE_NAME = "myapp"
def set_password(self, username: str, password: str):
# Stored in OS secure storage, not Python memory
keyring.set_password(self.SERVICE_NAME, username, password)
def get_password(self, username: str) -> str:
return keyring.get_password(self.SERVICE_NAME, username)
def delete_password(self, username: str):
keyring.delete_password(self.SERVICE_NAME, username)
Exploited in the Wild
Cold Boot Attacks
Researchers demonstrated recovering encryption keys from RAM by cooling memory chips and reading contents after reboot.
Swap File Analysis
Forensic analysis of swap files has revealed passwords, encryption keys, and sensitive documents.
Virtual Machine Memory Dumps
VM snapshots and memory dumps have exposed secrets stored in unlocked guest memory.
Tools to test/exploit
-
Volatility — memory forensics framework.
-
Cold Boot Tools — RAM forensics tools.
-
Swap Digger — Linux swap analysis tool.
-
Mimikatz — Windows credential extraction.
CVE Examples
-
CVEs involving recovery of credentials from swap space.
-
Encryption key exposure through memory dumps.
-
VM snapshot attacks exposing sensitive data.
References
-
MITRE. "CWE-591: Sensitive Data Storage in Improperly Locked Memory." https://cwe.mitre.org/data/definitions/591.html
-
Cold Boot Attacks on Encryption Keys. Princeton/EFF Research.