David Chisnall considera que o desafio mais difícil no isolamento de software não está em separar completamente duas cargas de trabalho, mas em isolar componentes que precisam se comunicar e compartilhar dados. Em uma apresentação publicada pela InfoQ, Chisnall explica como a arquitetura CHERI combina conceitos de capacidades de segurança e ponteiros estendidos para fornecer proteção de hardware aos ponteiros, com suporte à segurança da memória e ao isolamento preciso dentro do mesmo espaço de endereços.
Chisnall é cofundador e diretor de engenharia de sistemas da SCI Semiconductor, além de atuar como pesquisador visitante na Universidade de Cambridge. A apresentação baseia-se nos trabalhos de pesquisa sobre CHERI e em suas implementações de software e hardware, do CheriBSD e das extensões ARM Morello ao CHERIoT, voltado para microcontroladores, além da base RISC-V denominada RVY, cujo trabalho de padronização, segundo ele, estava em seus estágios finais.
O ponteiro torna-se uma entidade protegida por hardware
A CHERI não apresenta um conjunto de instruções independente no sentido tradicional, mas um conjunto de ideias que pode ser adaptado a diferentes arquiteturas, de modo semelhante à forma como os conceitos de SIMD são aplicados em várias extensões. Segundo essa concepção, o ponteiro deixa de ser apenas um número que representa um endereço na memória e passa a ser uma capacidade que contém o endereço, os limites do objeto e um conjunto de permissões, além de uma marca de hardware que determina se o valor é um ponteiro válido.
Essas capacidades impõem propriedades unidirecionais: é possível estreitar os limites ou remover permissões, mas não ampliá-los nem restaurá-los a partir do próprio ponteiro. O hardware também verifica se o ponteiro é válido e está marcado antes de usá-lo em operações de carregamento, armazenamento ou salto. Se um programa tentar usar um valor tratado como dados ou manipulado para produzir um endereço, a operação falha, em vez de convertê-lo automaticamente em um ponteiro utilizável.
Segundo a apresentação, esse design oferece proteção direta contra várias etapas comuns de ataques de exploração de memória, como sobrescrever um ponteiro com um valor controlado pelo invasor. Ele também permite operações de cópia genéricas, como memcpy, sem exigir conhecimento prévio dos locais dos ponteiros, pois a marca do ponteiro é transferida junto com o valor ou apagada quando parte do espaço da capacidade é sobrescrita.
Limites e permissões que podem ser reduzidos
As capacidades CHERI incluem permissões de leitura e escrita, além de permissões separadas para lidar com ponteiros presentes nos dados. Chisnall explica que essa precisão permite, por exemplo, conceder a um programa permissão para ler os dados dentro de uma estrutura complexa sem conceder a capacidade de armazenar novos ponteiros nela. Dessa forma, é possível fornecer uma visão somente leitura que se estende por uma árvore de dados inteira, em vez de proteger superficialmente um único objeto.
O mecanismo de sealing também permite criar ponteiros opacos seguros, semelhantes a identificadores ou descritores de arquivo, mas sem necessariamente exigir uma tabela de tradução administrada pelo kernel. A capacidade é selada usando um código de tipo, de modo que não possa ser modificada, desreferenciada ou usada para saltos, carregamentos e armazenamentos enquanto estiver selada. Quando é devolvida à entidade proprietária, a operação de desselagem só é bem-sucedida se o código de tipo coincidir.
Segurança espacial e temporal da memória
Em casos de erros de ultrapassagem de limites, a CHERI pode transformar um comportamento indefinido na linguagem C em uma interrupção determinística de hardware. Chisnall apresenta o exemplo de um vetor com 14 elementos no qual se tenta acessar o elemento 14: em um sistema tradicional, esse acesso pode resultar na sobrescrita do endereço de retorno, enquanto em um sistema CHERI a operação é interceptada porque o ponteiro está limitado aos limites do vetor.
A proposta não se limita à segurança espacial. No CHERIoT, as marcas dos ponteiros e uma memória sombra contendo um bit para cada unidade de alocação são usadas para invalidar ponteiros associados a memória que foi liberada. Quando uma capacidade é carregada para um registrador, o filtro de carregamento pode verificar o estado do bloco e apagar a marca se a memória tiver sido liberada. Um mecanismo periódico de hardware também examina a memória e remove as marcas dos ponteiros que apontam para objetos que já não estão vivos, permitindo reutilizar a memória após a conclusão do ciclo de invalidação.
Da segurança da memória ao isolamento preciso
O objetivo original do projeto CHERI, conforme apresentado por Chisnall, era construir um isolamento preciso que fosse além dos limites do isolamento tradicional de processos. Isolar cada componente em um processo separado impõe custos relacionados a tabelas de páginas, mecanismos de RPC, serialização e cópia de dados. Isso pode ser aceitável para dezenas de domínios isolados, mas se torna menos adequado quando os navegadores precisam, por exemplo, executar milhares ou dezenas de milhares de caixas de proteção para componentes como decodificação de imagens e áudio ou execução de JavaScript JIT.
O modelo CHERI permite considerar como domínio de proteção o conjunto de memória que pode ser alcançado a partir das capacidades presentes nos registradores de execução. Dois domínios dentro do mesmo espaço de endereços podem compartilhar alguns dados enquanto mantêm outras partes isoladas, pois o compartilhamento começa a partir de capacidades específicas e pode se estender a estruturas de dados complexas e a tudo o que pode ser alcançado a partir delas.
Em sistemas maiores, a apresentação explica um caminho gradual por meio da chamada coexecve, que cria um novo processo sem criar um novo espaço de endereços e, em seguida, fixa um conjunto de capacidades-raiz que define o que o novo processo pode acessar. Já o CHERIoT adota uma separação rigorosa de permissões dentro de um sistema operacional de tempo real, com um componente de troca responsável pela troca de contexto e pelas chamadas entre compartimentos, cujo tamanho é de cerca de 350 instruções, segundo a apresentação.
A conclusão apresentada por Chisnall é que a proteção da memória e o isolamento não deveriam depender da reescrita completa das regras do código nem da separação de cada componente em um processo independente. Manter o modelo de C próximo do habitual, tornando os ponteiros e as capacidades de acesso compreensíveis e controlados por hardware, pode oferecer um caminho gradual para reduzir erros de memória e construir um compartilhamento mais auditável. Os detalhes da implementação continuam diferentes entre sistemas maiores e microcontroladores, e a apresentação reflete o ponto de vista de seu autor e sua experiência direta com CHERI, não um padrão geral que garanta automaticamente a eliminação de todos os problemas de segurança.