
Análise aprofundada e exploit de prova de conceito para CVE-2022-0847 (DirtyPipe), uma vulnerabilidade do kernel Linux que permite sobrescrita arbitrária de arquivos e escalonamento local de privilégios por meio de flags de buffer de pipe não inicializadas.
title: Análise da vulnerabilidade CVE-2022-0847 (DirtyPipe - Escalação de privilégio local) date: 2022-03-08 14:41:20 tags: - Escalação de privilégio Linux categories: - Pesquisa de Segurança
CVE-2022-0847é uma vulnerabilidade no kernelLinuxdesde5.8. Um invasor pode usar esta vulnerabilidade para sobrescrever dados em qualquer arquivo somente leitura. Isso permite que um processo sem privilégios eleve seus privilégios pararoot, pois um processo não privilegiado pode injetar código no processo raiz.
CVE-2022-0847é semelhante aoCVE-2016-5195 "Dirty Cow"(escalada de privilégio Dirty Cow) e é fácil de explorar. O autor da vulnerabilidade a nomeouDirty Pipe.
Este blog é usado principalmente para registrar eventos de segurança e artigos sobre vulnerabilidades, para que todos possam estudar, comunicar e testar. Quaisquer consequências ou danos diretos ou indiretos causados pela disseminação ou uso das informações ou ferramentas fornecidas neste artigo serão de responsabilidade do usuário. O autor do artigo não assume qualquer responsabilidade por isso.
Nível de risco: Alto
POC/EXP: Público
Versões afetadas: Kernel Linux 5.8 e versões posteriores
Nota: Versões seguras: Linux kernel >= 5.16.11, Linux kernel >= 5.15.25, Linux kernel >= 5.10.102
Aqui está uma breve introdução aos detalhes da vulnerabilidade.
Alguns conceitos:
Linux pipe: Semiduplex, o fluxo de dados só pode ir de uma extremidade para a outra.
pipe_buffer: Buffer do pipe, usado para armazenar temporariamente os dados gravados no pipe. As operações de leitura e gravação são realizadas no buffer do pipe.
page: Moldura de página, 4 KB, com uma relação um-para-um com o buffer do pipe.
pipe_buf_operations: Usado para armazenar o conjunto de operações do buffer do pipe.
can_merge: Flag de merge. Se a leitura/gravação do pipe puder ser mesclada, este sinalizador é definido como 1 para permitir que os dados sejam anexados ao buffer existente. Se definido como 0, um novo segmento de página de pipe é sempre usado para novos dados.
splice(): Move dados entre dois descritores de arquivo, assim como a função sendfile(). Também suporta pipes e é zero-copy. Vincula o cache de página do arquivo ao buffer do pipe, ou seja, a gravação afetará ambos simultaneamente. Ao verificar permissões, apenas verifica se o arquivo de origem dos dados tem permissão de leitura; não há verificação de permissão para gravação. A cadeia de chamadas aproximada é:
// fs/splice.c
syscall --> do_splice --> do_splice_to --> splice_read(generic_file_splice_read()) --> call_read_iter(generic_file_read_iter)
// linux/mm/filemap.c
generic_file_read_iter --> filemap_read --> copy_folio_to_iter
// linux/lib/iov_iter.c
copy_folio_to_iter --> __copy_folio_to_iter --> copy_page_to_iter_pipe

Histórico do mecanismo de "merge" de pipe do Linux:
Aqui está uma breve análise do código com base na introdução do autor.
O sistema Linux inicial e o conceito introduzido são os mesmos: existe o flag can_merge, usado para marcar se novos dados podem ser gravados no buffer do pipe já existente.
O Commit 5274f052e7b3 adicionou a função splice(), mas a verificação não mudou: ainda usa o flag can_merge para determinar se o buffer do pipe atual pode ser usado.


O Commit 01e7187b4119 parou de usar o flag can_merge e passou a comparar o ponteiro struct pipe_buf_operations. Como apenas o tipo anon_pipe_buf_ops permite que novos dados sejam gravados, basta verificar se é esse tipo.


O Commit 241699cd72a8 adicionou duas novas funções que podem alocar novos struct pipe_buf_operations, mas não inicializam seu flag flags.

O Commit f6dd975583bd converteu a comparação de ponteiros em uma comparação do flag PIPE_BUF_FLAG_CAN_MERGE de cada buffer e permitiu a injeção de PIPE_BUF_FLAG_CAN_MERGE, ao mesmo tempo que removeu a definição e o uso de outros tipos de buf_ops.



Portanto, o autor tem a seguinte ideia de exploração:
PIPE_BUF_FLAG_CAN_MERGE do pipe_buffersplice ligará o buffer do pipe ao cache da página.write eventualmente chama copy_page_from_iter() para realizar a gravação. Devido ao flag PIPE_BUF_FLAG_CAN_MERGE, a gravação é concluída diretamente.Análise do Exp
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2022 CM4all GmbH / IONOS SE
*
* author: Max Kellermann <[email protected]>
*
* Proof-of-concept exploit for the Dirty Pipe
* vulnerability (CVE-2022-0847) caused by an uninitialized
* "pipe_buffer.flags" variable. It demonstrates how to overwrite any
* file contents in the page cache, even if the file is not permitted
* to be written, immutable or on a read-only mount.
*
* This exploit requires Linux 5.8 or later; the code path was made
* reachable by commit f6dd975583bd ("pipe: merge
* anon_pipe_buf*_ops"). The commit did not introduce the bug, it was
* there before, it just provided an easy way to exploit it.
*
* There are two major limitations of this exploit: the offset cannot
* be on a page boundary (it needs to write one byte before the offset
* to add a reference to this page to the pipe), and the write cannot
* cross a page boundary.
*
* Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
*
* Further explanation: https://dirtypipe.cm4all.com/
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort(); // Cria p[0] e p[1] apontando para as duas extremidades do pipe. O primeiro é leitura, o segundo é gravação.