Virando a página: a nova vulnerabilidade do Linux em `nf_tables` e técnicas de exploit mais robustas
(pwning.tech)- CVE-2024-1086 no
nf_tablesdo kernel Linux cria um double free desk_buffdevido a uma falha de validação de entrada no verdict do Netfilter e, nas condições certas, pode levar a escalonamento local de privilégios - O fluxo do ataque faz com que um skb liberado durante o processamento de
NF_DROPcontinue sendo tratado como se fosseNF_ACCEPT, de modo que o mesmo objeto acaba sendo liberado novamente em um caminho posterior - O PoC foi validado com mitigação do KernelCTF, Debian, Ubuntu e kernels vanilla, e pelo menos o intervalo v5.14.21~v6.6.14 pode ser afetado dependendo do
kconfig; a correção foi distribuída nos branches stable em fevereiro de 2024 - O ponto central é o Dirty Pagedirectory, que duplica a alocação de páginas PTE e PMD na mesma página física para acessar endereços físicos arbitrários apenas com leitura/escrita no espaço de usuário, em uma abordagem KSMA somente de dados
- A técnica ainda combina varredura de KASLR físico, desvio de
modprobe_path, execução sem arquivos e encadeamento de descritores de arquivo para escape de namespaces, formando um caso prático de LPE que mira ao mesmo tempo o gerenciamento de memória do kernel e o subsistema de rede
Condições da vulnerabilidade e escopo de impacto
- O bug em
nf_tablesfoi registrado como CVE-2024-1086 e tem como núcleo uma falha de validação de entrada que permitia erro positivo de drop no processamento de verdicts do Netfilter no kernel Linux - O exploit exige as seguintes condições
nf_tablesprecisa estar ativado- namespace de usuário não privilegiado precisa estar ativado
- Em distribuições principais como Debian e Ubuntu, onde essa configuração costuma vir habilitada por padrão, isso faz parte do pressuposto do ataque
- Com base nos testes, os branches stable
linux-5.15.y,linux-6.1.y,linux-6.6.yestão no escopo afetado, elinux-6.7.1também pode estar - A correção do bug foi distribuída nos branches stable em fevereiro de 2024
- O código-fonte do PoC está publicado em CVE-2024-1086 PoC repository
Fluxo de código que gera o double free
- Um verdict do Netfilter é o valor que decide se um pacote será descartado, aceito, enviado para fila etc.
- O fluxo vulnerável começa porque
nft_verdict_init()não restringia de forma suficiente o valor de verdict fornecido pelo usuário, permitindo configurar um valor que pareciaNF_DROP, mas cujo drop error era positivo nf_hook_slow()olha para os bits inferiores do verdict e, ao interpretá-lo comoNF_DROP, libera primeiro o skb comkfree_skb_reason()- Depois, se o resultado de
NF_DROP_GETERR()retornar um valor correspondente aNF_ACCEPT, o lado chamador continua o processamento como se o pacote tivesse sido aceito - Como resultado, o skb já liberado é liberado novamente em um caminho posterior, criando uma primitive de double free
Objetos corrompidos e forma de processamento de pacotes
- O double free afeta
struct sk_buffdeskbuff_head_cachee o objetosk_buff->head sk_buff->headcontém o conteúdo real do pacote e, dependendo do tamanho do pacote IPv4, pode ser alocado desdekmalloc-256até páginas order 4 do buddy allocator- O PoC foi projetado para usar pacotes IP grandes e assim seguir pelo caminho do buddy allocator, e não do slab allocator
- A fila de fragmentação do IPv4 é usada para atrasar a segunda liberação do skb ou induzi-la no momento desejado
- Como o uso de campos corrompidos do skb no caminho do pacote pode causar kernel panic, o exploit evita as stacks TCP/UDP e usa um caminho específico de erro de fragmentos IP
Escopo dos testes e taxa de sucesso
- Foram testados vários kernels e configurações em ambientes vanilla, KernelCTF, Debian e Ubuntu
- Os casos de sucesso incluem os seguintes ambientes
- Linux v5.14.21, v5.15.148, v5.16.20, v5.17.15, v5.18.19, v5.19.17, v6.0.19
- Linux v6.1.55 com KernelCTF Mitigation v3
- Linux v6.1.69 no Debian Bookworm 6.1.0-17
- Linux v6.1.72 no KernelCTF LTS
- Ubuntu Jammy v6.2.0-37
- Linux v6.2.16, v6.3.13
- Os casos de falha incluem v5.4.270, v5.10.209, v6.4.16, Ubuntu Jammy v6.5.0-15, v6.5.13, v6.6.14 e v6.7.1
- Parte das falhas após v6.4.0 está ligada à detecção de bad_page() causada por
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y - No ambiente v6.4.16, a taxa de sucesso foi de 99,4% e, em alguns casos, caiu até 93,0%, com tamanho de amostra n=1000 em ambos
Forma de correção
- A correção inicialmente proposta poderia introduzir uma breaking change no meio da stack do Netfilter
- A correção do maintainer do Netfilter restringe com mais rigor os verdicts vindos da entrada do usuário já na camada de API
- O patch rejeita parâmetros de verdict DROP/QUEUE vindos do userland
- Segundo a descrição do CVE,
nft_verdict_init()permitia valores positivos no drop error dentro do hook verdict, enf_hook_slow()podia gerar double free quandoNF_DROPeNF_ACCEPTse sobrepunham - O patch de correção pode ser visto em [PATCH nf] netfilter: nf_tables: reject QUEUE/DROP verdict parameters.
Dirty Pagedirectory e acesso arbitrário à memória física
- A técnica central do PoC é o Dirty Pagedirectory, uma variação da técnica existente Dirty Pagetable
- A ideia principal é duplicar a alocação de páginas PTE e PMD na mesma página física
- Ao gravar em uma região de endereço virtual valores que parecem entradas de PTE, outra região virtual passa a interpretá-los como entradas de tabela de páginas e mapeia a página física escolhida
- Dessa forma, a técnica funciona como um kernel-space mirroring attack, definindo endereços físicos arbitrários e flags de permissão por meio apenas de leitura/escrita em endereços do espaço de usuário
- Ela é usada para contornar mitigação como virtual KASLR, KPTI, SMAP, SMEP e
CONFIG_STATIC_USERMODEHELPER
Manipulação do page allocator
- A alocação de páginas no kernel envolve slab allocator, buddy allocator e PCP allocator
- O head do skb usa páginas order 4 no buddy allocator quando o tamanho é grande, mas páginas PTE/PMD são páginas order 0, então elas não se encaixam diretamente
- Para resolver isso, são usados dois métodos de conversão de páginas
- PCP list draining: coloca páginas order 4 na freelist do buddy e esvazia a freelist PCP order 0, forçando o buddy allocator a reabastecê-la com páginas order 0
- race condition: usa uma disputa durante a segunda liberação para inserir a página order 4 na freelist order 0
- O método de PCP list draining é mais simples, estável e rápido
- O método baseado em race condition foi usado no exploit inicial do KernelCTF, mas passou a ser considerado obsoleto por depender de ambientes com grande latência de serial TTY, como VMs QEMU
Desvio das mitigação do KernelCTF
- No ambiente com mitigação do KernelCTF, a proteção que mais exigiu desvio foi a checagem de corrupção da freelist de
sk_buff - Como
skbuff_head_cache->offset == 0x70, o ponteiro next da freelist se sobrepõe askb->len - Após a primeira liberação do skb,
skb->lené parcialmente sobrescrito por parte do ponteiro da freelist e, se esse valor mudar durante o parsing posterior do pacote, a checagem de corrupção pode disparar - O desvio consiste em liberar um skb normal adicional sobre o skb corrompido para sobrescrever a cabeça da freelist e evitar a detecção
- Desenvolvedores do KernelCTF observaram que verificar também o ponteiro next da cabeça da freelist no momento da liberação poderia mitigar esse desvio
TLB flush e busca do KASLR físico
- Quando o Dirty Pagedirectory altera tabelas de páginas de forma inesperada, o cache de TLB da CPU pode manter traduções antigas
- O TLB é limpo fazendo
fork()no espaço de usuário, deixando o filho executarmunmap()e depois dormir - Esse método foi confirmado como funcional em 100% dos casos em CPUs AMD e em VMs QEMU
- O KASLR físico é buscado aproveitando o fato de que o endereço base físico do kernel é alinhado conforme
CONFIG_PHYSICAL_STARTouCONFIG_PHYSICAL_ALIGN - Assumindo 8GiB de memória física e alinhamento de 16MiB, há 512 candidatos; o script get-sig é usado para gerar a assinatura do base do kernel e identificá-lo
modprobe_path e obtenção de shell root
- Depois de conseguir leitura/escrita arbitrária na memória física, o PoC varre cerca de 80MiB após o base do kernel para localizar
modprobe_path - Em configurações comuns, ele procura o padrão
"/sbin/modprobe"com preenchimento nulo e valida a variável real verificando se ela se reflete em/proc/sys/kernel/modprobe - Quando
CONFIG_STATIC_USERMODEHELPERestá ativado, o alvo passa a ser a string"/sbin/usermode-helper" - Para obter um shell root, o exploit sobrescreve
modprobe_pathou a string do static usermode helper com um caminho memfd no formato/proc/<pid>/fd/<fd> - O script de escalonamento de privilégios conecta os descritores de arquivo do exploit ao stdin/stdout do shell, permitindo funcionamento tanto em terminal local quanto em reverse shell
Execução sem arquivos e composição do PoC
- O PoC suporta execução sem arquivos sem gravar nada no disco
- Se o alvo tiver Perl, é possível usar
memfd_create()para carregar o binário do exploit na memória e executá-lo via/proc/$$/fd/<fd> - As dependências de compilação são
libnftnl-develibmnl-dev - O build estático para KernelCTF usou
musl-gcc, escolhido para evitar problemas de linkagem estática com glibc e problemas com opcode AVX512 no QEMU - O código do exploit está dividido em vários arquivos e, por ter como objetivo um binário autônomo, em caso de erro prefere crash/exit a retornar códigos de erro
Estabilidade e limitações
- Como o estado das pagetables do processo do exploit pode ficar instável, após sucesso ou falha os processos filhos são deixados em sleep em vez de serem encerrados, reduzindo a instabilidade do kernel
- Atividade de rede pode introduzir ruído na freelist de skb e afetar a estabilidade
- Em ambientes com SSH ou reverse shell, a saída para stdout é reduzida perto do momento do double free para minimizar alocação/liberação de skb causada pela rede
- Em alguns testes de hardware, o sistema travou após alguns segundos, e como frames de Wi‑Fi também usam skb, a atividade de Wi‑Fi pode ter influenciado
- Desativar o adaptador Wi‑Fi na BIOS fez o exploit funcionar normalmente nesse ambiente
Conclusões obtidas durante a pesquisa
- O PoC foi refinado com foco em ampla compatibilidade, alta estabilidade e execução discreta
- Além de dois meses de desenvolvimento, foram usados mais dois meses para melhorar estabilidade e compatibilidade
- O exploit em si não depende fortemente do comportamento do slab allocator, e se concentra em recursos amplamente habilitados, como o subsistema IPv4 e a memória virtual
- Embora o bug inicial exija namespace de usuário não privilegiado e nftables, técnicas como Dirty Pagedirectory e PCP draining podem ser aproveitadas em outros exploits reais
- Este trabalho fica como um caso de exploração prática que investigou em conjunto o subsistema de rede e o subsistema de gerenciamento de memória do kernel Linux
1 comentários
Comentários do Hacker News
Hoje foi publicado um exploit de prova de conceito para CVE-2024-1086, que funciona em Debian, Ubuntu e outros
As versões afetadas são o kernel Linux v5.14 a v6.6, e o suporte a v6.4~v6.6 depende da configuração de kernel
CONFIG_INIT_ON_ALLOC_DEFAULT_ONEsse bug foi corrigido em fevereiro de 2024, então é preciso atualizar as máquinas Linux
https://github.com/Notselwyn/CVE-2024-1086/blob/main/src/mai...
modprobe_pathe até incluir força bruta de pid por causa da abordagem sem arquivoPor exemplo, gostaria de perguntar por que não escolheram uma abordagem de aplicar patch no
.textdo kernel com um shellcode curto para virar root e escapar do namespacePorém, está escrito que os branches corrigidos
v5.15.149>,v6.1.76>,v6.6.15>ficam excluídosHá esta frase no patch: “This reverts commit e0abdadcc6e1. [...] Its not clear to me why this commit was made.”
Fico curioso se alguém investigou o histórico de contexto desse commit
[1] https://lore.kernel.org/all/20240120215012.129529-1-fw@strle...
Procurei na antiga lista netdev, mas não encontrei nada; pode ter sido um patch enviado diretamente ao committer, Pablo Neira Ayuso
O autor original era Patrick McHardy, que hoje se tornou uma figura evitada, e, a menos que Pablo se lembre ou encontre nos e-mails, o caso de uso exato parece difícil de descobrir com uma investigação básica
netfilter: nf_tables: accept QUEUE/DROP verdict parameters, que permite ao espaço de usuário especificar um número de fila ou código errno nos veredictosQUEUEeDROPEste artigo também é muito impressionante do ponto de vista de redação sobre segurança
Ao escrever um blog de segurança, a dúvida é sempre “quanto conhecimento de contexto e prévio devo presumir?”, e é difícil encontrar um equilíbrio que seja acessível e ainda viável de escrever
Achei excelente o fato de definir primeiro o público-alvo e fornecer explicação de contexto suficiente; por isso, marquei como favorito para usar como material de orientação para pesquisadores iniciantes que encontro todo ano
Este exploit depende do acesso a namespaces de usuário sem privilégios:
sysctl kernel.unprivileged_userns_clone = 1Esse é o padrão nos kernels Debian/Ubuntu e Arch Linux, e é melhor desativá-lo se você não precisa, por exemplo, executar comandos Docker sem sudo
O sandbox do Chrome usado por apps Electron ou pelo 1Password também está relacionado, e o binário auxiliar do sandbox também pode funcionar como um programa setuid
Também não seria surpreendente se o Proton começasse a usar namespaces de usuário no futuro, então talvez seja melhor não desativar isso em um Linux desktop comum
Em servidores ou em Linux especialmente reforçado, desativar costuma não ser uma má ideia
O problema é que, para contêineres “simplesmente funcionarem”, são necessários inúmeros hacks na parte de rede, e o principal argumento de venda do Docker é quase fazer esses hacks perigosos por você
No fim, qualquer solução de contêiner acaba aceitando esses hacks e, mesmo que a funcionalidade normal de namespaces reduza o acesso, o kernel acaba abrindo a porta por causa dos hacks de rede
Não entendo por que namespaces de usuário sem privilégios vêm ativados por padrão
Mesmo que sejam executados dentro de um namespace “sem privilégios”, fico me perguntando qual é a razão para dar ao usuário, por padrão, a capacidade de executar coisas como iptables ou mount
Por exemplo, o Chrome usa namespaces para implementar o sandbox de processos, mas instala um binário setuid-root para funcionar mesmo sem namespaces sem privilégios
Como um binário setuid-root por si só é um risco de segurança, no longo prazo é desejável que o Chrome não precise instalar esse tipo de binário
Só que, para isso, namespaces de usuário sem privilégios precisam estar amplamente disponíveis, e bugs como este acabam atrasando esse futuro
Além disso, programas que montam sandboxes baseados em namespaces, como o Chrome, frequentemente usam seccomp junto para impedir que o código dentro do sandbox use funcionalidades incomuns do kernel, como namespaces
Em desktops de usuário único, o benefício de aplicar fortemente a separação entre usuário e root não é tão grande, e a maior parte do que é interessante também está acessível pela conta do usuário
Por outro lado, sandboxes como o do Chrome são essenciais para a segurança no desktop; portanto, em desktops de usuário único, tende-se a considerar que ativar namespaces de usuário sem privilégios aumenta a segurança geral
Sistemas multiusuário, naturalmente, são outra história
Se esses bugs simplesmente não continuassem aparecendo, namespaces de usuário sem privilégios seriam um excelente recurso de segurança
Por exemplo, seria bom se executar o Flatpak não exigisse um binário setuid do host para isolar apps
A maioria dos padrões não é segura e exige conhecimento técnico e trabalho de hardening
Commit que introduziu o problema: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
A estrutura parece estranha: há um
returnno caminho padrão dentro de umswitch/caseaninhado, mas depois se espera um fall-throughhttps://lwn.net/Articles/882397/
https://bugs.launchpad.net/bugs/cve/2024-1086
É uma vulnerabilidade de use-after-free no componente
netfilter: nf_tablesdo kernel Linux que permite escalação local de privilégiosA função
nft_verdict_init()permite valores positivos como erro de drop dentro de um veredicto de hook e, como resultado,nf_hook_slow()processaNF_DROPcom um erro de drop que pareceNF_ACCEPT, o que pode criar uma vulnerabilidade de double-freeRecomenda-se atualizar para uma versão posterior a
f342de4e2f33e0e39165d8639387aa6c19dff660Fico curioso sobre como esse tipo de exploit é possível mesmo com mitigações modernas como ASLR
Em uma disciplina da faculdade, recebemos vários binários para executar em uma versão específica do Ubuntu e tínhamos de encontrar bugs como use-after-free ou buffer overflow e explorá-los; foi realmente difícil
Encontrar a falha já era difícil, e escrever o shellcode exato que fizesse algo útil com ela era ainda mais difícil
Nas etapas mais difíceis, mitigações como ASLR e stack canary também estavam ativadas e, mesmo em um ambiente controlado para estudantes, parecia quase impossível
No fim, é preciso 1) descobrir uma falha explorável e 2) encontrar o payload binário exato que faça algo útil sem simplesmente derrubar o programa; no mundo real isso deve ser ainda mais difícil, então fica a dúvida de como é possível
[0] https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/a...
Paralelamente a outras disciplinas, o tempo disponível para cada bug provavelmente era de algumas horas a alguns dias
A Zerodium paga US$ 50 mil por uma escalação local de privilégios Linux comum, o que, em termos de custo de desenvolvedores de exploits experientes, compra aproximadamente 200 horas-pessoa
Ou seja, especialistas gastam de 10 a 100 vezes mais tempo que estudantes iniciantes
Pense na diferença entre alguém que vai a uma marcenaria pela primeira vez e um marceneiro, entre um iniciante em cerâmica e um artesão, entre um calouro em pintura e um artista profissional
Além disso, há 10 a 100 vezes mais tempo investido
[1] https://zerodium.com/program.html
Existem técnicas “clássicas” para burlar a maioria das proteções modernas e, quando não há, às vezes surgem novos ataques ou métodos de bypass
Por exemplo, para bypass de proteções de heap, dá para ver o how2heap[0]; também houve exemplos de exploits que contornam KASLR[1], e este exploit parece usar a técnica dirty pagetable[2]
A dinâmica é a de um jogo de gato e rato contínuo: mitigações são adicionadas e pesquisadores encontram formas de contorná-las
[0] https://github.com/shellphish/how2heap
[1] https://www.willsroot.io/2022/12/entrybleed.html
[2] https://pwning.tech/nftables/#452-the-technique
Para encontrar esse tipo de coisa, basta uma pessoa do último grupo
Hoje em dia é realmente difícil e, mesmo desativando as mitigações, encontrar vulnerabilidades e escrever exploits ainda não é nada fácil
Porém, muitas pessoas que encontram esse tipo de vulnerabilidade trabalham em equipes, paralelizam fuzzing e conseguem combinar ou encadear conhecimento e outros exploits
A especialização e o talento de alguns pesquisadores são impressionantes, e nessa área anos ou décadas de experiência têm um valor enorme
Mas, pensando que os principais bugs encontrados hoje são descobertos por equipes bem financiadas ou atores estatais, fica mais compreensível que seja possível investir muita mão de obra e recursos para contornar as mitigações existentes
Segundo a Ubuntu, todas as versões LTS são afetadas e a falha foi corrigida nos kernels atualmente corrigidos: https://ubuntu.com/security/CVE-2024-1086
No Focal, foi corrigida em
5.4.0-174.193; no Jammy, em5.15.0-101.111; no Mantic, em6.5.0-26.26Se você usa suporte estendido, Xenial e Bionic também estão incluídos
Testei em um sistema Debian vulnerável e, embora não tenha ocorrido elevação de privilégios, na segunda execução o sistema inteiro travou
A primeira execução simplesmente falhou, então ainda assim vale muito a pena dedicar um tempo para aplicar o patch
As configurações atuais do kernel podem ser verificadas em arquivos como
/boot/configou/proc/config.gz