
Linux LPE - Fuga Confiável de Jail/Container
Uma prova de conceito confiável de escape de contêiner / jail não privilegiado para CentOS / RHEL 10.
Ele explora um bug de fragmentação IPv6 agora corrigido em __ip6_append_data() (corrigido upstream por 38becddc, sem CVE), um estouro linear in-slab no skb_shared_info no final do objeto head do próprio pacote. Este README documenta apenas a cadeia de exploração. Não cobre o gatilho.
6.12.0-242.el10).Esta é uma prova de conceito, deliberadamente não uma arma pronta para uso. A estrutura de confiabilidade foi intencionalmente omitida: sem preparação de PCP por CPU, sem blindagem de SLUB cross-cache, e o head do skb é colocado em um cache compartilhado, não tão silencioso. O que é enviado funciona com frequência suficiente para provar a cadeia, e não muito mais. É limitado apenas ao CentOS / RHEL 10.
CONFIG_INIT_ON_ALLOC_DEFAULT_ON desligado, o padrão do RHEL/CentOS. Esta PoC planta um ponteiro obsoleto em bytes de slab não inicializados; com init_on_alloc=1 esse slot é zerado e esta técnica específica apenas derruba o kernel (um deref NULL). Isso é uma limitação da técnica, não do bug: uma exploração completa que também cubra esses kernels, com uma técnica diferente, seguirá assim que a correção e o CVE forem públicos./sys/kernel/btf/vmlinux presente e legível mundialmente (padrão no RHEL/CentOS).| Distribuição | Kernel | Resultado |
|---|---|---|
| CentOS Stream 10 | 6.12.0-242.el10 | root, escape de contêiner |
| RHEL 10 | 6.12.0-228.el10 | escape de contexto httpd_t |
Estouro in-slab para self-UAF. O bug dá controle sobre o único byte nr_frags em skb_shared_info. O caminho de liberação (skb_release_data()) percorre frags[0 .. nr_frags) e chama put_page() em cada entrada, e frags[] nunca é inicializado. Pré-plantamos um struct page * para uma página de buffer de pipe nesse slot de slab através de reuso controlado de cache, então definimos nr_frags como 1, para que a destruição libere uma referência em uma página que ainda possuímos. O estouro linear se torna um use-after-free de página. O estouro nunca toca frags[0], então não precisa ser sincronizado com o spray, que é a maior parte do que o torna tolerante.
UAF de página para Dirty-Pagetable. A página de pipe liberada é reivindicada como uma tabela de páginas de último nível ao causar falha em um novo mapeamento anônimo. Uma página física agora é simultaneamente uma tabela de páginas folha ativa e a página que o pipe ainda lê e escreve. Escrever oito bytes no pipe instala uma PTE forjada; ler o pipe lê a tabela de volta. Isso é uma leitura/escrita física arbitrária finita, na ordem de 460 janelas de PTE por tabela.
Derrotar o KASLR. Com a leitura física, varremos a tabela de páginas fixa do trampoline SMP de baixa memória, que o KASLR nunca realoca; sua entrada da metade do kernel aponta para , a base física do kernel. A partir daí, (reconhecível por sua entrada autorreferente 511) é um tradutor virtual para físico universal, recuperando a base virtual do kernel e amarrando nosso espaço de endereçamento à memória física.
level4_kernel_pgtinit_top_pgtLeitura/escrita finita para infinita. Forjamos uma PTE na tabela folha que aponta para o endereço físico da própria tabela. A janela virtual correspondente então cria um alias para a própria tabela de páginas, de modo que as PTEs se tornam memória comum: leitura/escrita de kernel ilimitada e de acesso aleatório, sem pipe no loop. A coerência TLB do Ring 3 é tratada forçando um flush completo do mm com um mprotect() superdimensionado.
Resolver offsets e obter credenciais. Os offsets dos membros da estrutura são lidos em tempo de execução de /sys/kernel/btf/vmlinux, não embutidos. Localizamos nosso próprio task_struct através da varredura de struct page do vmemmap e mm_struct.owner, zeramos os IDs de credenciais e preenchemos todos os conjuntos de capacidades em cred e real_cred. Os endereços dos símbolos vêm de um resolvedor interno que analisa as próprias tabelas kallsyms do kernel através de nossa leitura arbitrária; apenas se isso falhar, recorremos a apontar cred.user_ns para init_user_ns (para que CAP_SYSLOG seja válido no namespace inicial) e ler os endereços reais de /proc/kallsyms.
Desabilitar o SELinux sem alterar o modo enforcing. Sobrescrevemos o prólogo de avc_denied() com um stub xor eax, eax ; ret (pulando endbr64 sob IBT). Toda negação agora retorna concedida, em todo o processo, enquanto getenforce ainda reporta Enforcing. Isso também é um pré-requisito para o escape: a reexecução entre domínios do manipulador de core dump seria negada de outra forma.
Escape através do core_pattern. Sobrescrevemos a variável global core_pattern com um manipulador prefixado por | apontando de volta para nosso próprio binário através de /proc/<PID>/root (o chroot do contêiner da tarefa que está falhando), então derrubamos um filho. O kernel executa o manipulador como um usermodehelper, root nos namespaces iniciais, e retransmitimos um shell root interativo sobre um socket Unix vinculado dentro do contêiner. O manipulador nasce dentro dos namespaces init, então nenhuma cirurgia de namespace in-loco, e nenhum de seus perigos de namespace PID, é necessária.