
Ambiente com kernel vulnerável para exploração do driver TEE (CVE-2021-44733)
Recentemente, uma vulnerabilidade de use-after-free foi descoberta no subsistema TEE do kernel Linux, até e incluindo a versão 5.15.11, e recebeu o identificador CVE-2021-44733 [1].
À primeira vista, não parecia ser explorável por várias razões, no entanto, após uma análise mais aprofundada do caminho de código vulnerável e implementando um exploit de prova de conceito simplório, foi possível sobrescrever um ponteiro de função no kernel. Nenhum payload de escalonamento de privilégio é apresentado neste post, no entanto, todo o ambiente para executar o OPTEE e o exploit está disponível para testes adicionais, veja 'Configurando o ambiente'.
Um TEE (Trusted Execution Environment) é um SO confiável executando em algum ambiente seguro, por exemplo, TrustZone em CPUs ARM. Um driver TEE lida com os detalhes necessários para se comunicar com o TEE. Algumas das funções mais importantes do driver são fornecer uma API genérica para o TEE baseada na especificação Globalplatform TEE Client API [3], mas também gerenciar a memória compartilhada entre o Linux e o TEE. Este subsistema pode ser habilitado configurando CONFIG_OPTEE nas configurações do kernel para arquiteturas ARM.
O mundo seguro contém o SO confiável denominado OP-TEE OS [4]. Sobre este SO é possível ter as chamadas Aplicações Confiáveis (TAs) em execução, que podem realizar algumas operações no ambiente isolado, veja a Figura 1.
Figura 1: Visão geral do TEE - da apresentação da Linaro [5]
O mundo normal (userspace/kernel Linux) pode interagir com essas aplicações usando aplicações cliente (CAs) e a API exposta pelo subsistema TEE. Uma CA pode abrir uma sessão para uma TA específica e invocar funções que a TA implementa. A passagem de argumentos entre a TA e a CA é feita usando memória compartilhada. A interação entre uma CA e uma TA usando todas as syscalls relevantes é descrita a seguir.
Uma CA abre /dev/tee[0-9] para se comunicar com o driver. Note que, para a forma convencional de usar essas APIs, isso é feito implicitamente usando o libteec.
A memória compartilhada pode ser registrada pela CA usando o IOCTL TEE_IOC_SHM_ALLOC. Isso aloca memória compartilhada e retorna um descritor de arquivo que o espaço do usuário pode usar como parte do mmap.
O próximo passo é estabelecer uma sessão usando o IOCTL TEE_IOC_OPEN_SESSION e especificando o uuid para uma TA específica. Este uuid é hardcoded durante a compilação da TA.
Para invocar qualquer função específica na TA, a CA a invoca especificando o identificador de uma função juntamente com quaisquer argumentos de entrada, isso é feito usando TEE_IOC_INVOKE.
Quando a CA terminar todas as requisições, a sessão pode ser fechada usando TEE_IOC_CLOSE_SESSION.
Figura 2: Sessão entre CA e TA - da apresentação da Linaro [5]
Grande parte da comunicação entre clientes e o TEE é opaca para o driver. O principal trabalho do driver é gerenciar o contexto, receber requisições dos clientes, encaminhá-las para o TEE e enviar de volta os resultados [2].