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

ImpactDetails
ConfidentialityScope: Information Disclosure

Sensitive data written to swap is accessible to attackers.
SecurityScope: Credential Exposure

Passwords and keys may be recovered from disk.
ComplianceScope: 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


CVE Examples

  • CVEs involving recovery of credentials from swap space.

  • Encryption key exposure through memory dumps.

  • VM snapshot attacks exposing sensitive data.


References

  1. MITRE. "CWE-591: Sensitive Data Storage in Improperly Locked Memory." https://cwe.mitre.org/data/definitions/591.html

  2. Cold Boot Attacks on Encryption Keys. Princeton/EFF Research.