
CVE-2023-3269: Vulnerabilidade de escalonamento de privilégios no kernel Linux
(Exploit verificado pelo GitHub-CI)
Foi encontrada uma falha no tratamento da expansão da pilha no kernel Linux 6.1 até 6.4, também conhecida como "Stack Rot". A árvore de bordo (maple tree), responsável por gerenciar as áreas de memória virtual, pode passar por uma substituição de nós sem adquirir adequadamente o bloqueio de escrita do MM, gerando problemas de uso-após-liberação. Um usuário local sem privilégios poderia usar essa falha para comprometer o kernel e escalar seus privilégios.
Como o StackRot é uma vulnerabilidade do kernel Linux encontrada no subsistema de gerenciamento de memória, ela afeta quase todas as configurações do kernel e exige capacidades mínimas para ser acionada. No entanto, deve-se observar que os nós de bordo são liberados usando callbacks RCU, atrasando a desalocação real de memória até depois do período de graça do RCU. Consequentemente, explorar essa vulnerabilidade é considerado desafiador.
Até onde sei, atualmente não existem exploits publicamente disponíveis que
visem bugs de uso-após-liberação-por-RCU (UAFBR). Esta é a primeira instância
em que bugs UAFBR foram comprovadamente exploráveis, mesmo sem a presença das
configurações CONFIG_PREEMPT ou CONFIG_SLAB_MERGE_DEFAULT. Notavelmente,
este exploit foi demonstrado com sucesso no ambiente fornecido pelo Google kCTF
VRP (bzImage_upstream_6.1.25, config).
A vulnerabilidade StackRot está presente no kernel Linux desde a versão 6.1, quando a estrutura da árvore VMA foi alterada de árvores rubro-negras para árvores de bordo.
Sempre que a chamada de sistema mmap() é utilizada para estabelecer um
mapeamento de memória, o kernel gera uma estrutura chamada vm_area_struct
para representar a área de memória virtual (VMA) correspondente. Essa estrutura
armazena diversas informações, incluindo flags, propriedades e outros detalhes
pertinentes relacionados ao mapeamento.```c
struct vm_area_struct {
long unsigned int vm_start; /* 0 8 /
long unsigned int vm_end; / 8 8 /
struct mm_struct * vm_mm; / 16 8 /
pgprot_t vm_page_prot; / 24 8 /
long unsigned int vm_flags; / 32 8 /
union {
struct {
struct rb_node rb attribute((aligned(8))); / 40 24 /
/ --- cacheline 1 boundary (64 bytes) --- /
long unsigned int rb_subtree_last; / 64 8 /
} attribute((aligned(8))) shared attribute((aligned(8))); / 40 32 /
struct anon_vma_name * anon_name; / 40 8 /
} attribute((aligned(8))); / 40 32 /
/ --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- /
struct list_head anon_vma_chain; / 72 16 /
struct anon_vma * anon_vma; / 88 8 /
const struct vm_operations_struct * vm_ops; / 96 8 /
long unsigned int vm_pgoff; / 104 8 /
struct file * vm_file; / 112 8 /
void * vm_private_data; / 120 8 /
/ --- cacheline 2 boundary (128 bytes) --- /
atomic_long_t swap_readahead_info; / 128 8 /
struct vm_userfaultfd_ctx vm_userfaultfd_ctx; / 136 0 */
/* size: 136, cachelines: 3, members: 14 */
/* forced alignments: 1 */
/* last cacheline: 8 bytes */
} attribute((aligned(8)));
Posteriormente, quando o kernel encontra falhas de página ou outras chamadas de sistema
relacionadas à memória, ele exige uma busca rápida da VMA baseada apenas no endereço.
Anteriormente, as VMAs eram gerenciadas usando árvores rubro-negras. No entanto, a partir
da versão 6.1 do kernel Linux, ocorreu a migração para maple trees. [Maple
trees][mt] são estruturas de dados B-tree seguras para RCU, otimizadas para armazenar
intervalos sem sobreposição. No entanto, sua natureza intrincada adiciona complexidade ao
código e introduz a vulnerabilidade StackRot.
[mt]: https://docs.kernel.org/6.4/core-api/maple_tree.html
Em sua essência, uma maple tree é composta por nós maple. Embora a estrutura da árvore
possa ser complexa, é importante observar que essa complexidade não tem nada a ver com
o bug StackRot. Portanto, ao longo deste artigo, assume-se que a maple tree consiste
em apenas um nó, ou seja, o nó raiz.
Esse nó raiz pode conter até 16 intervalos. Esses intervalos podem
representar uma lacuna ou apontar para uma VMA. Como as lacunas também contam
como intervalos, todos os intervalos são conectados sequencialmente, resultando
na necessidade de apenas 15 extremidades, também conhecidas como pivôs, dentro
da estrutura do nó. Observe que a extremidade mais à esquerda e a extremidade
mais à direita são omitidas, pois podem ser recuperadas do nó pai.```c
struct maple_range_64 {
struct maple_pnode * parent; /* 0 8 */
long unsigned int pivot[15]; /* 8 120 */
/* --- cacheline 2 boundary (128 bytes) --- */
union {
void * slot[16]; /* 128 128 */
struct {
void * pad[15]; /* 128 120 */
/* --- cacheline 3 boundary (192 bytes) was 56 bytes ago --- */
struct maple_metadata meta; /* 248 2 */
}; /* 128 128 */
}; /* 128 128 */