Introduction

In the realm of software development, especially when handling sensitive data, memory security is paramount. Developers often encounter situations where they need to protect sensitive information such as license keys or passwords from prying eyes. A question that arises in such contexts is: How can we create a secure memory allocator in C++ that prevents paging to disk and is difficult to access through debuggers?

In this blog post, we will delve into the issues surrounding memory security and explore how to implement a C++ allocator that meets these requirements.

Understanding the Challenges

Before we dive into the solution, it is essential to understand some of the challenges that come with protecting memory:

  • Preventing Paging to Disk: On systems that use virtual memory, memory can be paged to disk, making it vulnerable to unauthorized access. When memory is paged out, it’s stored in swap space, which might be accessible to other processes.

  • Access Through Debuggers: Debuggers can access and manipulate memory, making it crucial to find ways to minimize this risk, especially for sensitive information.

  • OS-Level Limitations: Regardless of our intentions, the operating system has control over processes and can potentially read memory contents.

A Secure Memory Allocator in C++

Despite these challenges, we can implement a custom allocator in C++ using Windows API functions. Let’s break down the solution step-by-step:

1. Using VirtualAlloc

The primary tool for allocating memory securely is VirtualAlloc. This function allows us to allocate memory in a specified way:

  • We can set protection levels to control how the allocated memory can be accessed.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);

Here, allocType is set to MEM_COMMIT, and allocProtect is set to PAGE_READWRITE, enabling both read and write access.

2. Locking Memory

To prevent the OS from paging the memory to disk, we use the VirtualLock function:

if (pMem != NULL) {
    ::VirtualLock(pMem, allocLen);
}

However, keep in mind that VirtualLock imposes constraints on the amount of memory you can allocate, which may be manageable depending on your needs.

3. Deallocating Memory Securely

When deallocating memory, it’s essential to zero out the sensitive contents before freeing it. This can be done using SecureZeroMemory:

::SecureZeroMemory(_pPtr, allocLen);
::VirtualUnlock(_pPtr, allocLen);
::VirtualFree(_pPtr, 0, MEM_RELEASE);

Complete Example

Here is the complete implementation of our secure memory allocator:

template<class _Ty>
class LockedVirtualMemAllocator : public std::allocator<_Ty> {
public:
    template<class _Other>
    LockedVirtualMemAllocator<_Ty>& operator=(const LockedVirtualMemAllocator<_Other>&) {
        return (*this);
    }

    template<class Other>
    struct rebind {
        typedef LockedVirtualMemAllocator<Other> other;
    };

    pointer allocate(size_type _n) {
        SIZE_T allocLen = (_n * sizeof(_Ty));
        DWORD allocType = MEM_COMMIT;
        DWORD allocProtect = PAGE_READWRITE;
        LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);
        if (pMem != NULL) {
            ::VirtualLock(pMem, allocLen);
        }
        return reinterpret_cast<pointer>(pMem);
    }

    void deallocate(void* _pPtr, size_type _n) {
        if (_pPtr != NULL) {
            SIZE_T allocLen = (_n * sizeof(_Ty));
            ::SecureZeroMemory(_pPtr, allocLen);
            ::VirtualUnlock(_pPtr, allocLen);
            ::VirtualFree(_pPtr, 0, MEM_RELEASE);
        }
    }
};

// Usage Example
typedef std::basic_string<char, std::char_traits<char>, LockedVirtualMemAllocator<char>> modulestring_t;

Conclusion

Creating a secure memory allocator in C++ is a complex task that involves navigating various system-level constraints and security challenges. While it is impossible to achieve total protection against memory access, using functions like VirtualAlloc, VirtualLock, and SecureZeroMemory can significantly enhance the security of sensitive data.

It’s essential to bear in mind that no system can be entirely secure from the owner of the device. Thus, understanding these limitations allows developers to create more robust and resilient applications.

For anyone interested in deeper insights, resources such as Practical Cryptography by Neil Ferguson and Bruce Schneier can provide valuable context in cryptographic practices and secure memory management methodologies.