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.