Mitigações de Segurança (ASLR, Stack Canaries, NX/SMEP/SMAP)
Paginação, a GDT, e chamadas de sistema são todos mecanismos dos quais um kernel precisa simplesmente para funcionar; as mitigações que este artigo cobre são diferentes, restrições que um kernel impõe a si mesmo que o hardware não exige estritamente para operação correta, adotadas especificamente para tornar um bug de segurança de memória mais difícil de virar um exploit funcional, não para fazer algo funcionar.
Address Space Layout Randomization carrega o código de um binário, suas bibliotecas, sua stack, e seu heap em endereços escolhidos de forma nova e aleatória a cada execução, em vez de nos mesmos endereços fixos toda vez. Isso mira diretamente no próprio mapeamento de segmentos PT_LOAD de ELF Loading: um exploit que depende de saltar para um endereço específico e conhecido (uma função de biblioteca em particular, ou um pedaço de shellcode injetado sentado num offset previsível da stack) precisa primeiro ou adivinhar esse endereço corretamente ou vazá-lo através de algum outro bug, em vez de simplesmente fixar no código um valor que funciona identicamente em toda máquina vulnerável rodando o mesmo binário. A proteção real do ASLR só é tão forte quanto a entropia por trás dela: um esquema de aleatorização semeado de uma fonte fraca ou previsível pode ser quebrado por força bruta ou adivinhado na prática mesmo que o mecanismo em si seja sólido, motivo exato pelo qual a geração de números aleatórios em hardware importa diretamente aqui, não como uma preocupação tangencial.
Stack canaries
Seção intitulada “Stack canaries”Um stack canary é um valor sentinela conhecido que o compilador coloca na stack, entre os buffers locais de uma função e seu endereço de retorno salvo, sempre que -fstack-protector (ou uma de suas variantes mais rígidas, -fstack-protector-strong/-all) está habilitado. O epílogo da função checa esse valor contra o original antes de retornar, e um stack buffer overflow clássico, longo o suficiente para sobrescrever o endereço de retorno salvo e redirecionar a execução, precisa sobrescrever o canary primeiro para alcançá-lo, corrompendo um valor que o epílogo está especificamente vigiando.
void vulnerable(char *input) { char buf[64]; strcpy(buf, input); // um overflow aqui sobrescreve o canary antes do endereço de retorno return; // o epílogo checa o canary; uma discrepância aborta em vez de retornar}Um canary discrepante no momento do retorno significa que o programa aborta imediatamente em vez de continuar executando com um endereço de retorno corrompido, transformando o que de outra forma seria um sequestro silencioso e explorável de fluxo de controle num crash barulhento e diagnosticável em vez disso. O próprio valor do canary é deliberadamente imprevisível (comumente contendo um byte nulo especificamente para também quebrar overflows ingênuos baseados em string que dependem de funções como strcpy pararem em um) e regenerado por processo a partir da própria fonte aleatória do kernel, então um atacante não consegue simplesmente fixar no código uma constante conhecida da forma que um exploit desprotegido contra um layout de stack fixo de outra forma conseguiria.
NX, SMEP, e SMAP
Seção intitulada “NX, SMEP, e SMAP”NX (No-eXecute) é um único bit numa entrada de tabela de página que, quando definido, faz a CPU recusar executar qualquer instrução buscada daquela página independentemente de que dado esteja sentado ali, fechando uma classe histórica inteira de exploit que funcionava injetando shellcode executável num buffer gravável (a stack, ou o heap) e saltando diretamente para ele. Marcar toda página gravável como não executável e toda página executável como não gravável (uma disciplina comumente chamada de W^X, “write xor execute”) é o que de fato realiza a proteção do NX na prática: o bit em si não faz nada a menos que a própria política de mapeamento de memória de um kernel evite consistentemente jamais criar uma página que seja simultaneamente gravável e executável, já que NX só bloqueia execução onde o kernel de fato definiu o bit, não por padrão algum que o hardware imponha sozinho.
SMEP (Supervisor Mode Execution Prevention) e SMAP (Supervisor Mode Access Prevention) estendem a mesma ideia através da fronteira ring 3/ring 0 em vez de dentro de um único espaço de endereçamento: SMEP faz a CPU falhar se código de ring 0 algum dia tentar executar uma instrução de uma página marcada como acessível ao usuário, e SMAP a faz falhar também numa leitura ou escrita comum de ring 0 para tal página (um kernel que genuinamente precisa tocar memória de usuário, copiando um buffer de argumento de syscall, por exemplo, precisa usar uma sequência de instrução dedicada que temporariamente suspende a restrição, em vez de acessá-la através de uma desreferência de ponteiro comum). Os dois fecham um padrão de exploit relacionado onde fluxo de controle de kernel corrompido é redirecionado para código ou dado controlado pelo atacante que nunca foi colocado em memória de kernel alguma, só em memória que o exploit já controla inteiramente a partir do ring 3, o que NX sozinho, restrito às permissões de página de um único espaço de endereçamento, não impede.
Notas de implementação
Seção intitulada “Notas de implementação”Nenhuma dessas mitigações é completa isoladamente, e nenhuma pretende ser: ASLR sem NX ainda permite injeção de código em endereços previsíveis uma vez vazados, NX sem ASLR ainda permite return-oriented programming construído inteiramente a partir de código já existente e já executável que o atacante nunca precisou injetar, e stack canaries protegem só o padrão específico de overflow de sobrescrever um endereço de retorno salvo através de um estouro contíguo de buffer, não outros bugs de segurança de memória como um use-after-free ou uma escrita fora dos limites no heap. Um kernel habilitando todas juntas está deliberadamente elevando o custo e a complexidade que um exploit precisa em cada estágio individual para ter sucesso, não eliminando bugs de segurança de memória na origem, o que continua sendo um problema de corretude que nenhuma mitigação aqui de fato corrige.
Referências
Seção intitulada “Referências”- ^ PaX Team, PaX address space layout randomization (ASLR): o documento de design original do ASLR, mais tarde adotado amplamente por kernels mainstream.
- ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Capítulo 4.6: define o bit NX, e os bits de habilitação SMEP/SMAP de CR4.
Ver também
Seção intitulada “Ver também”- Paging & Virtual Memory: a estrutura de tabela de página da qual o bit NX é ele mesmo um campo.
- ELF Loading: o mapeamento de segmento PT_LOAD cujo posicionamento o ASLR diretamente aleatoriza.
- Geração de Números Aleatórios em Hardware: a fonte de entropia da qual a imprevisibilidade real do ASLR depende.