Análise aprofundada do novo syscall `mseal` do Linux
(blog.trailofbits.com)- O mseal do Linux 6.10 é um syscall de mitigação de exploits que sela áreas de memória virtual em execução, dificultando que um invasor que já obteve permissão de execução de código altere depois as permissões ou o layout das VMAs
mseal(start, len, flags)adicionaVM_SEALEDa um intervalo de VMA alinhado a páginas, e o kernel passa a rejeitar mudanças em caminhos comomprotect,munmap,mmap(MAP_FIXED),mremape alguns caminhos destrutivos demadvise- Enquanto
memfd_createememfd_secretestão mais próximos de arquivos anônimos baseados em RAM e controle de acesso a memória secreta, o mseal foca em impedir mudanças de permissão, unmapping e remapping após execução de código por um invasor remoto - Os principais alvos de defesa são ataques que contornam NX com
mprotect(PROT_EXEC)para executar shellcode e exploits somente de dados que criam buracos na memória communmap/remapeamento e depois os preenchem com dados controlados pelo atacante - Há possibilidade de integração com a glibc a partir da 2.41, mas stack e heap não são alvos de selagem automática por causa da expansão e contração em tempo de execução, então os desenvolvedores precisam definir com cuidado quando e onde aplicar isso
Proteção oferecida pelo mseal
- Memory sealing é um recurso que torna um intervalo de VMA em execução quase imutável diante de modificações perigosas posteriores
- Mesmo que um invasor obtenha um primitivo de execução de código, em uma VMA selada fica difícil alterar permissões ou manipular o layout da memória virtual a seu favor
- A equipe de segurança do Chrome introduziu esse syscall para dar suporte à estratégia de CFI do V8 no ChromeOS baseado em Linux
- Depois de várias discussões e reescritas, ele entrou no kernel Linux e pode ampliar seu uso além dos navegadores por meio da integração com a glibc
Diferença em relação à família memfd
memfd_createememfd_secretestão mais próximos da família de file sealing- Permitem criar arquivos anônimos baseados em RAM
memfd_secretfaz com que apenas processos com o descritor de arquivo possam acessar aquela região de memória- Podem ser usados em mapeamentos em espaço de usuário no estilo “secure enclave” para proteger dados sensíveis em memória
- O
msealé um syscall voltado para mitigação de exploits contra invasores remotos que buscam execução de código, mais do que contra invasores locais interessados em vazamento de informações sensíveis
Funcionamento interno no kernel
- A assinatura do syscall é
int mseal(unsigned long start, size_t len, unsigned long flags)startelenrepresentam o início e o tamanho de uma VMA válida a ser seladalenprecisa estar corretamente alinhado a páginas- No momento,
flagsnão é usado e deve ser0
- Na implementação do Linux 6.12, ele chama
do_msealcurrent->mmaponta paramm_struct, que representa todo o espaço de endereços de memória virtual do processo chamador- O
mmapdemm_structcontém a lista devm_area_struct, que representam regiões contínuas de memória criadas commmap - Regiões como stack ou VDSO também podem ser representadas como uma única VMA
do_msealcalcula o endereço final do intervalo e então bloqueia a região de memória commmap_write_lock_killablecheck_mm_sealpercorre cada VMA do intervalo alvo e primeiro valida os limites- Se houver memória não alocada no meio do intervalo, retorna
-ENOMEM - Essa primeira checagem evita que apenas parte das VMAs seja selada em caso de erro
- Se houver memória não alocada no meio do intervalo, retorna
apply_mm_sealpercorre o mesmo intervalo novamente e adiciona a flagVM_SEALEDàs VMAs alvo por meio demseal_fixup
Operações de memória proibidas após a selagem
- O patchset do kernel adiciona verificações de
VM_SEALEDemmm/madvise.c,mm/mmap.c,mm/mprotect.c,mm/mremap.cemm/mseal.c mprotectepkey_mprotectchamam internamentecan_modify_vmaemmprotect_fixup, e retornam-EPERMse a VMA estiver selada- Em uma VMA selada, as seguintes operações não são permitidas
- Mudança de bits de permissão via
mprotectoupkey_mprotect - Unmapping via
munmap - Substituição de um mapeamento selado por um mapeamento variável e não selado via
mmap(MAP_FIXED) - Expansão ou redução de tamanho via
mremap - Movimentação para um novo destino via
mremap(MREMAP_MAYMOVE | MREMAP_FIXED) madvisecom algumas flags destrutivas
- Mudança de bits de permissão via
- Na movimentação com
mremap, a checagem de selagem se aplica tanto à VMA de origem quanto à de destino - Sem
MREMAP_DONTUNMAP, a VMA de origem é desmontada, e isso também precisa passar pela checagem de selagem demunmap - No Linux 6.10 ou superior, já é possível usar
msealcom chamada direta de syscall, e o wrapper de exemplo usa o número de syscall462comflags=0
Bloqueio de execução de shellcode por reforço de NX
- Mesmo com técnicas de reutilização de código como ROP, um invasor ainda pode preferir executar shellcode
- Espalha shellcode em uma stack ou heap não executável
- Executa uma cadeia ROP inicial por meio de uma vulnerabilidade
- Desativa o bit NX da região com shellcode usando
mprotect(PROT_EXEC) - Salta para essa região e executa o shellcode
- O ataque ao daemon SMB do MikroTik RouterOS usando CVE-2018-7445 é um exemplo desse fluxo
- Shellcode baseado em socket é colocado em um heap não executável
- Uma cadeia ROP criada por stack overflow altera as permissões da memória do heap
- Depois disso, o shellcode é executado
- Se a VMA correspondente for selada com
mseal, a checagemcan_modify_vmabloqueia a mudança de permissão durante a chamada demprotect - No código de exemplo, se a página de shellcode na stack não for selada, ela pode ser executada após
mprotect(PROT_READ|PROT_WRITE|PROT_EXEC) - Se a mesma página for selada com
mseal,mprotectnão consegue alterar as permissões de fato, e a execução do shellcode resulta em falha de segmentação
Limitações na aplicação em stack e heap
- Quando
msealentrar na glibc a partir da 2.41, o loader dinâmico deverá aplicar selagem a um conjunto previamente definido de VMAs - No plano atual, stack e heap não serão selados automaticamente
- Stack e heap podem crescer em tempo de execução, e a selagem automática pode quebrar o funcionamento da aplicação
- O alocador de heap pode chamar o syscall
brkpara recuperar espaço - Nesse caminho, a redução pode ocorrer passando por
arch_unmapedo_vmi_unmap - Com a selagem ativa, esse unmapping não é permitido, o que pode quebrar a alocação dinâmica de memória
- O alocador de heap pode chamar o syscall
- Os desenvolvedores precisam avaliar, no contexto da aplicação, em que momento e em quais regiões a selagem pode ser aplicada com segurança
- O macro de exemplo sela repetidamente as páginas de frames de stack selecionados que podem conter dados não confiáveis
- Chamar
msealnovamente em uma VMA já selada vira um no-op e não gera erro - Recursos como crescimento automático da stack ou stack splitting exigem atenção extra
- Se o invasor insistir em shellcode, ainda pode criar uma nova região executável com
mmape copiar o payload de uma região legível, mas o processo fica mais trabalhoso
Mitigação de exploits somente de dados baseados em unmapping
- Bloquear
mprotecttambém impede que regiões seladas se tornem graváveis, o que ajuda a proteger variáveis de dados que, se modificadas, poderiam fortalecer primitivos de exploit - Mantenedores do Chrome consideraram a técnica de passar ponteiros corrompidos para syscalls de unmapping/remapeamento para abrir buracos na memória e depois preenchê-los com dados controlados pelo atacante
- Como essa abordagem não altera diretamente transferências de fluxo de controle, como endereços de retorno da stack ou ponteiros de função, ela pode contornar garantias de CFI tanto em forward-edge quanto em backward-edge
- Em navegadores com compiladores JIT, essa técnica pode ser especialmente útil
- O Turbofan do V8 pode criar regiões que alternam entre RW e RX
- O invasor pode induzir a geração de código executável com JavaScript hot-path durante a compilação JIT
- Depois de preencher a região desmontada com dados que sobrescrevem informações importantes, pode produzir uma mudança que leve à execução de código
- Trata-se de um exploit somente de dados que influencia o fluxo de controle manipulando dados específicos na memória, sem exigir sequestro direto do fluxo de controle nem vazamento prévio de ponteiros
- O
msealpode bloquear esse cenário de hole-punching ao impedir unmapping e remapping
Caso House of Muney
- House of Muney usa uma técnica semelhante baseada em unmapping em exploração de heap em espaço de usuário
- A Qualys usou essa técnica em um exploit real para um bug antigo do Qmail
- A técnica depende do fato de que chunks grandes de alocação, quando ultrapassam
M_MAP_THRESHOLD, fazem com quemallocefreechamem diretamentemmapemunmap - Como chunks grandes não têm cache intermediário de freelist, o procedimento de exploração fica mais simples
- Se o metadado de tamanho no topo do chunk alocado for adulterado para outro tamanho de página e depois for feito
free, pode ocorrermunmapsobre uma região de memória adjacente ao chunk - O exemplo de Dulin mira as regiões
.gnu.hashe.dynsymcommunmaparbitrário- Depois as preenche novamente com um chunk
mmapmaior - Isso permite sobrescrever uma entrada de PLT ainda não resolvida
- E revive um ataque no estilo overwrite de GOT
- Depois as preenche novamente com um chunk
- O PoC resumido aloca dois chunks grandes, manipula o campo
sizedetop[-1], fazfree(top)para desmontar até a região adjacente e então preenche novamente com dadosXusando uma alocação maior - A técnica pode funcionar mesmo com CFI e não exige vazamento prévio de ASLR
- Espera-se que a selagem do conjunto de VMAs planejada na integração do
msealcom a glibc mitigue automaticamente esse ataque ao proteger código binário mapeado e bibliotecas dinâmicas contra truques de unmapping/remapping - Desenvolvedores que quiserem reforço adicional podem selar seletivamente alocações
mmapque não serão expandidas nem desmontadas durante a vida útil do programa
Uso futuro
- O
msealé uma funcionalidade de mitigação relativamente nova no kernel Linux, e pode haver mais casos de uso que ainda não foram explorados - À medida que a integração com a glibc for concluída e amadurecer, podem surgir melhorias para adequá-lo melhor às exigências do syscall
- O parâmetro
flags, atualmente sem uso, também pode ganhar uma função mais concreta no futuro - Ao aplicar isso em software real, é preciso avaliar tanto o ciclo de vida da memória selada quanto a necessidade de alterações nela
1 comentários
Opiniões no Hacker News
Seria bom se alguém de dentro resumisse quais foram as objeções e preocupações nas discussões acaloradas da lista de e-mails do kernel
Eu costumo evitar a própria lista de e-mails porque às vezes ela é pesada demais, e já tenho dores de cabeça suficientes
O mecanismo em si parece razoável, mas é surpreendente que esse recurso ainda não existisse no kernel
Entre as flags propostas havia uma que permitiria ignorar o seal, e Linus reagiu mais ou menos como: “quer dizer que em um lugar você impede
munmap, mas em outro quer ignorar o seal?”Depois ele foi ainda mais enfático, dizendo que “uma vez selado, está selado” e que não dá para ter “alguns lugares respeitando o seal e outros lugares arbitrários não”
Mais tarde, chegou a dizer que seals não podem ser ignorados, não são negociáveis, e que colocaria na ignore list quem continuasse propondo uma flag para ignorá-los
Em um e-mail de Theo de Raadt para Jeff Xu, respondendo à afirmação de que, embora
mimmutable()emseal()tenham abordagens diferentes, ambos têm o objetivo de tornar aplicações Linux mais seguras ao selar a memória contra invasores, ele disse: “parece que você está criando o mseal só para o Chrome”Ele considerou que isso não se encaixaria bem para aplicações em geral, citando como motivos o excesso de complexidade e, pela experiência com
mimmutable(), o fato de que isso acaba ficando a cargo deexecve(), da inicialização da libc e dold.so, em vez de ser tratado diretamente pelas aplicaçõesEle concordou com Theo e Linus, considerando que a ideia básica de
mimmutable()é boa, mas que a forma como ela foi dividida e implementada é terrívelFico curioso sobre como se usa essa syscall
O Chrome quer isso, mas como um atacante poderia remapear com outras flags, páginas seladas não podem ser desmapeadas
Então, para páginas alocadas em runtime, parece que elas praticamente não podem ser usadas a menos que você pretenda mantê-las durante toda a vida do processo
Nesse caso, não seria difícil aplicar isso a alvos muito atraentes, como a memória do sandbox de JS?
Fico me perguntando se esse tipo de coisa é resolvido executando o trabalho em um processo separado, selando a memória e depois matando o processo quando terminar
Não conheço bem o gerenciamento de memória e processos do Chrome, então não tenho certeza de por que isso não seria um problema, e também me pergunto se é por isso que esse recurso é frequentemente descrito como pouco útil para a maioria dos programas
Pelo que sei, o Chrome usa isso amplamente, e talvez esse seja o caminho, já que processos separados de qualquer forma são necessários por motivos como isolamento via namespaces
Discussão após o
mseal(), 20 de outubro de 2023: https://lwn.net/Articles/948129/O
mseal()se aproxima, 19 de janeiro de 2024: https://lwn.net/Articles/958438/Selagem de memória para a GNU C Library, 12 de junho de 2024: https://lwn.net/Articles/978010/
É uma pena que o sistema operacional precise implementar chamadas desse tipo, mesmo com a arquitetura x86_64 moderna tendo muitos recursos que ajudam na programação e na computação seguras
Acho que a atitude de tentar remendar sistemas antigos, construídos sobre legados e mentalidades ultrapassados e sobre paradigmas que não se ajustam ao mundo e ao conhecimento atuais, atrasa o avanço da computação e literalmente coloca bilhões de pessoas em risco
Claro, não quero dizer que essa mudança não seja um passo na direção certa, mas, se abandonarmos o antigo ideal de que o sistema operacional simplesmente precisa funcionar e considerarmos os sistemas e conhecimentos atuais e o que as pessoas querem deles, é possível imaginar sistemas livres do ônus e do risco impostos hoje a desenvolvedores e usuários
É verdade que existem bugs de arquitetura, mas o software nem sequer aproveita corretamente os recursos atuais; portanto, usar bugs de arquitetura como contra-argumento foge do ponto central
Se for construído sobre uma base instável, sempre haverá uma forma mais barata de invasão
Também gostaria de entender por que você acha que
msealnão se justifica e o que seria melhor em seu lugarSerá que dá para sobrescrever ou desativar a chamada de sistema
msealcom o truque deLD_PRELOAD?msealé uma chamada de sistema voltada mais para mitigar ataques de execução remota de código do que para impedir um atacante local de extrair segredos sensíveis da memóriaSe um atacante remoto consegue alterar o ambiente local, então já se deve considerar que ele invadiu o sistema
LD_PRELOADPara surtir efeito, teria que ser uma função importada, mas chamadas de sistema brutas não podem ser interceptadas dessa forma
Discussão sobre interceptação de chamadas de sistema no Linux: https://stackoverflow.com/questions/69859/how-could-i-interc...
Criar você mesmo um kernel com patch que finja que
msealfunciona pode ser a maneira mais fácil de “desativar” o recursoPorém, como programas que usam
msealpodem verificar se ele realmente está funcionando, um kernel comprometido também precisaria de um modo de desativarmsealàs escondidas para impedir as verificações do app após a aplicaçãoPor exemplo, é possível rastrear o executável com
ptracepara monitorar chamadas de sistema e fazer com quemseal(2)seja puladaEssa chamada de sistema não foi pensada para um modelo de ameaça em que “o atacante já tinha acesso antes da inicialização do processo”
msealpode ser sobrescrito, mas a chamada de sistema em si nãoPelo que encontrei, todos os métodos de sobrescrita de chamadas de sistema via preload trocam o wrapper, e parece que não dá para sobrescrever quando a chamada de sistema é feita diretamente
Tecnicamente, talvez seja possível sobrescrever a própria função
syscallMeta: o protótipo de
mseal()mostrado no texto precisa ser editadoO primeiro argumento aparece como
unsigned start addr, mas provavelmente o correto éunsigned long start_addrint mseal(unsigned long start, size_t len, unsigned long flags)Em “Memory Sealing ‘Mseal’ System Call Merged for Linux 6.10” (2024) https://news.ycombinator.com/item?id=40474510#40474551, já houve uma discussão sobre como o CPython deveria dar suporte à chamada de sistema
mseal()É um recurso que já existia há muito tempo no OpenBSD https://man.openbsd.org/mimmutable.2
Fico me perguntando por que uma funcionalidade tão óbvia só está chegando agora ao Linux
mimmutabledo OpenBSD foi introduzido no OpenBSD 7.3, lançado em 10 de abril de 2023, então não é algo que exista “há muito tempo”Por outro lado, Linux e FreeBSD já tinham
memfd_createhá muito tempo, enquanto o OpenBSD não tem arquivos anônimos e depende deshm_open