
Uma primitiva de exploit no kernel Linux inspirada no DirtyPipe
Uma primitiva de exploit no kernel Linux inspirada no DirtyPipe (CVE-2022-0847).
Recentemente, tal como muitos veteranos de segurança, estudei e reproduzi a vulnerabilidade DirtyPipe (CVE-2022-0847) e senti profundamente como esta falha é útil. A falha começa num problema de memória não inicializada e termina na modificação de arquivos arbitrários, e, pelo caminho, não envolve leak de KASLR nem operações como ROP, JOP. Portanto, não é necessário contornar proteções como SMEP, SMAP.
Depois de reproduzi-la, comecei a pensar: por que o DirtyPipe é tão útil? Será que, com a correção do DirtyPipe, ele desaparecerá como um clarão e não terá qualquer valor de aprendizado para explorações futuras?
De repente, percebi que a estrutura onde o DirtyPipe reside — struct pipe_buffer — me parecia muito familiar. Ah! Não é exatamente a estrutura alocada com a flag GFP_KERNEL_ACCOUNT, localizada no slab-1k e que contém um campo ops que costumo usar para fazer leak de KASLR e sequestro de RIP?
Em seguida, senti-me um idiota: por que eu estava modificando o ops do pipe_buffer para fazer ROP? Bastava modificar as flags do pipe_buffer e combinar com splice para conseguir logo escrita em arquivos arbitrários! Assim, não precisaria de fazer leak de KASLR, nem de adaptar o exploit para diferentes versões do kernel, nem de me preocupar com gadgets, nem de contornar proteções como SMEP, SMAP e KPTI.
Peguei imediatamente os exploits que havia reproduzido e estudado antes — CVE-2021-22555 e outros que envolvem a estrutura pipe_buffer — e, com pequenas alterações, consegui explorá-los com sucesso em diferentes versões do kernel sem correção. Seguem alguns exemplos:
Como até então não tinha visto ninguém usar isso como uma primitiva em exploits do kernel Linux, tomei a liberdade de chamá-la de “Pipe Primitive” XD.
Resumindo, é preciso obter a capacidade de modificar a estrutura Pipe, por exemplo, através de um UAF no slab-1k, ou de uma escrita fora dos limites no slab-1k com deslocamento (ou seja, não um overflow direto em sequência).
Assim, no kernel >= 5.8, basta modificar flag |= PIPE_BUF_FLAG_CAN_MERGE na página splice dentro do pipe_buffer (se tiver capacidade, pode também alterar offset e len para 0, escrevendo assim desde o início do arquivo); no kernel < 5.8, é preciso primeiro fazer leak de anon_pipe_ops no pipe_buffer e depois alterar o ops da página splice para anon_pipe_ops (porque em versões < 5.8, a possibilidade de merge depende do ops) (se tiver capacidade, também pode alterar offset e len para 0).
Quanto a você perguntar como fazer o escape de contêiner nesse caso? O que eu faria seria mais ou menos como no CVE-2019-5736 XD.