Introdução

No âmbito do desenvolvimento de software, especialmente ao lidar com dados sensíveis, a segurança da memória é primordial. Os desenvolvedores frequentemente se deparam com situações onde precisam proteger informações sensíveis, como chaves de licença ou senhas, de olhos curiosos. Uma pergunta que surge em tais contextos é: Como podemos criar um alocador de memória seguro em C++ que previna a paginação para o disco e seja difícil de acessar por meio de depuradores?

Neste post do blog, vamos explorar as questões relacionadas à segurança da memória e investigar como implementar um alocador em C++ que atenda a esses requisitos.

Compreendendo os Desafios

Antes de mergulharmos na solução, é essencial entender alguns dos desafios que acompanham a proteção da memória:

  • Prevenir a Paginação para o Disco: Em sistemas que utilizam memória virtual, a memória pode ser paginada para o disco, tornando-se vulnerável a acessos não autorizados. Quando a memória é paginada, ela é armazenada no espaço de troca, que pode ser acessível a outros processos.

  • Acesso por Meio de Depuradores: Depuradores podem acessar e manipular a memória, tornando crucial encontrar maneiras de minimizar esse risco, especialmente para informações sensíveis.

  • Limitações em Nível de SO: Independentemente de nossas intenções, o sistema operacional controla os processos e pode potencialmente ler o conteúdo da memória.

Um Alocador de Memória Seguro em C++

Apesar desses desafios, podemos implementar um alocador personalizado em C++ usando funções da API do Windows. Vamos detalhar a solução passo a passo:

1. Usando VirtualAlloc

A ferramenta principal para alocar memória de forma segura é VirtualAlloc. Essa função nos permite alocar memória de uma maneira específica:

  • Podemos definir níveis de proteção para controlar como a memória alocada pode ser acessada.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);

Aqui, allocType é definido como MEM_COMMIT, e allocProtect é definido como PAGE_READWRITE, permitindo acesso tanto de leitura quanto de escrita.

2. Bloqueando a Memória

Para impedir que o SO pagine a memória para o disco, usamos a função VirtualLock:

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

No entanto, tenha em mente que VirtualLock impõe restrições sobre a quantidade de memória que você pode alocar, o que pode ser gerenciável dependendo de suas necessidades.

3. Desalocando Memória de Forma Segura

Ao desalocar a memória, é essencial zerar os conteúdos sensíveis antes de liberá-los. Isso pode ser feito usando SecureZeroMemory:

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

Exemplo Completo

Aqui está a implementação completa do nosso alocador de memória seguro:

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);
        }
    }
};

// Exemplo de Uso
typedef std::basic_string<char, std::char_traits<char>, LockedVirtualMemAllocator<char>> modulestring_t;

Conclusão

Criar um alocador de memória seguro em C++ é uma tarefa complexa que envolve navegar por várias restrições e desafios de segurança em nível de sistema. Embora seja impossível alcançar uma proteção total contra acesso à memória, usar funções como VirtualAlloc, VirtualLock e SecureZeroMemory pode aumentar significativamente a segurança de dados sensíveis.

É essencial ter em mente que nenhum sistema pode ser totalmente seguro contra o proprietário do dispositivo. Portanto, entender essas limitações permite que os desenvolvedores criem aplicações mais robustas e resilientes.

Para aqueles que estão interessados em uma compreensão mais profunda, recursos como Practical Cryptography de Neil Ferguson e Bruce Schneier podem fornecer um contexto valioso em práticas criptográficas e metodologias de gerenciamento seguro de memória.