Para mitigar a divulgação do kernel ou da memória entre processos (o ataque Spectre ), o kernel 1 do Linux será compilado com uma nova opção , -mindirect-branch=thunk-extern
apresentada gcc
para executar chamadas indiretas por meio do chamado retpoline .
Esse parece ser um termo recém-inventado, pois uma pesquisa no Google mostra apenas um uso muito recente (geralmente tudo em 2018).
O que é um retpoline e como ele evita os recentes ataques de divulgação de informações do kernel?
1 No entanto, não é específico do Linux - construções semelhantes ou idênticas parecem ser usadas como parte das estratégias de mitigação em outros sistemas operacionais.
security
assembly
x86
cpu-architecture
BeeOnRope
fonte
fonte
gcc
aponte dessa maneira! Eu não reconheci lkml.org/lkml/2018/1/3/780 como no site Linux Kernel Mailing List, nem mesmo uma vez que olhei lá (e recebi uma captura instantânea, pois estava offline).Respostas:
O artigo mencionado por sgbj nos comentários escritos por Paul Turner, do Google, explica o seguinte com muito mais detalhes, mas vou tentar:
Tanto quanto eu posso juntar isso a partir das informações limitadas no momento, um retpoline é um trampolim de retorno que usa um loop infinito que nunca é executado para impedir que a CPU especule sobre o alvo de um salto indireto.
A abordagem básica pode ser vista na ramificação do kernel de Andi Kleen, que trata desse problema:
Ele introduz a nova
__x86.indirect_thunk
chamada que carrega o destino da chamada cujo endereço de memória (que eu chamareiADDR
) está armazenado no topo da pilha e executa o salto usando umaRET
instrução. O thunk em si é chamado usando a macro NOSPEC_JMP / CALL , que foi usada para substituir muitas (se não todas) chamadas e saltos indiretos. A macro simplesmente coloca o destino da chamada na pilha e define o endereço de retorno corretamente, se necessário (observe o fluxo de controle não linear):A colocação de
call
no final é necessária para que, quando a chamada indireta for concluída, o fluxo de controle continue por trás do uso daNOSPEC_CALL
macro, para que possa ser usado no lugar de uma consulta regular.call
O thunk em si tem a seguinte aparência:
O fluxo de controle pode ficar um pouco confuso aqui, então deixe-me esclarecer:
call
empurra o ponteiro de instrução atual (etiqueta 2) para a pilha.lea
adiciona 8 ao ponteiro da pilha , descartando efetivamente a palavra-chave empurrada mais recentemente, que é o último endereço de retorno (no rótulo 2). Depois disso, a parte superior da pilha aponta para o endereço de retorno real ADDR novamente.ret
pula*ADDR
e redefine o ponteiro da pilha para o início da pilha de chamadas.No final, todo esse comportamento é praticamente equivalente a pular diretamente para
*ADDR
. O único benefício que obtemos é que o preditor de ramificação usado para instruções de retorno (Buffer de Pilha de Retorno, RSB), ao executar acall
instrução, assume que aret
instrução correspondente passará para o rótulo 2.A parte depois que o rótulo 2 nunca é executado, é simplesmente um loop infinito que, em teoria, preencheria o pipeline de
JMP
instruções com instruções. Ao usarLFENCE
,PAUSE
ou mais geralmente, uma instrução que faz com que o pipeline de instruções fique parado impede a CPU de desperdiçar energia e tempo nessa execução especulativa. Isso ocorre porque, caso a chamada para retpoline_call_target retorne normalmente, essaLFENCE
seria a próxima instrução a ser executada. Isso também é o que o preditor de ramificação irá prever com base no endereço de retorno original (o rótulo 2)Para citar o manual de arquitetura da Intel:
Observe, no entanto, que a especificação nunca menciona que LFENCE e PAUSE causam a interrupção do pipeline, por isso estou lendo um pouco entre as linhas aqui.
Agora, de volta à sua pergunta original: A divulgação de informações da memória do kernel é possível devido à combinação de duas idéias:
Embora a execução especulativa deva ser livre de efeitos colaterais quando a especulação estiver errada, a execução especulativa ainda afeta a hierarquia do cache . Isso significa que, quando uma carga de memória é executada especulativamente, ela ainda pode ter causado a remoção de uma linha de cache. Essa alteração na hierarquia de cache pode ser identificada medindo cuidadosamente o tempo de acesso à memória que é mapeado no mesmo conjunto de cache.
Você pode até vazar alguns bits de memória arbitrária quando o endereço de origem da memória lida foi ele próprio lido na memória do kernel.
O preditor de ramificação indireta das CPUs Intel usa apenas os 12 bits mais baixos da instrução fonte, portanto, é fácil envenenar todos os 2 ^ 12 históricos de previsão possíveis com endereços de memória controlados pelo usuário. Estes podem então, quando o salto indireto é previsto dentro do kernel, ser executado especulativamente com privilégios de kernel. Usando o canal lateral de tempo de cache, você pode vazar memória arbitrária do kernel.
ATUALIZAÇÃO: Na lista de discussão do kernel , há uma discussão em andamento que me leva a crer que os retpolines não atenuam completamente os problemas de previsão de ramificação, como quando o RSB (Return Stack Buffer) fica vazio, as arquiteturas Intel mais recentes (Skylake +) retornam ao vulnerável Target Target Buffer (BTB):
fonte
push
/ret
que não desequilibra a pilha preditora de endereço de retorno. Há uma imprevisibilidade (ir paralfence
antes de o endereço de retorno real ser usado), mas usar umacall
modificação +rsp
equilibrou issoret
.push
/ret
(no meu último comentário). re: sua edição: o fluxo insuficiente de RSB deve ser impossível porque o retpoline inclui acall
. Se a preempção do kernel fizesse uma mudança de contexto lá, retomaríamos a execução com o RSB preparado docall
para o planejador. Mas talvez um manipulador de interrupção possa terminar comret
s suficientes para esvaziar o RSB.Um retpoline foi projetado para proteger contra a exploração de injeção de destino do ramo ( CVE-2017-5715 ). Este é um ataque no qual uma instrução de ramificação indireta no kernel é usada para forçar a execução especulativa de um pedaço arbitrário de código. O código escolhido é um "gadget" que é de alguma forma útil para o invasor. Por exemplo, o código pode ser escolhido para vazar os dados do kernel através de como isso afeta o cache. O retpoline impede essa exploração simplesmente substituindo todas as instruções de ramificação indireta por uma instrução de retorno.
Eu acho que o principal do retpoline é apenas a parte "ret", que substitui o ramo indireto por uma instrução de retorno, para que a CPU use o preditor de pilha de retorno em vez do preditor de ramo explorável. Se uma simples instrução push e return fosse usada, o código que seria executado especulativamente seria o código para o qual a função retornará de qualquer maneira, não algum dispositivo útil para o invasor. O principal benefício da parte do trampolim parece ser a manutenção da pilha de retorno. Assim, quando a função realmente retorna ao chamador, isso é previsto corretamente.
A idéia básica por trás da injeção de alvo de ramificação é simples. Aproveita o fato de a CPU não registrar o endereço completo da origem e destino das ramificações em seus buffers de destino da ramificação. Portanto, o invasor pode preencher o buffer usando saltos em seu próprio espaço de endereço, o que resultará em acertos de previsão quando um salto indireto específico for executado no espaço de endereço do kernel.
Observe que o retpoline não impede a divulgação direta de informações do kernel, apenas impede que instruções de ramificação indireta sejam usadas para executar especulativamente um gadget que divulgaria informações. Se o invasor puder encontrar outros meios para executar especulativamente o gadget, o retpoline não impedirá o ataque.
O artigo Specter Attacks: Explorating Speculative Execution de Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz e Yuval Yarom dão a seguinte visão geral de como as ramificações indiretas podem ser exploradas :
Uma entrada de blog intitulada Lendo memória privilegiada com um canal lateral da equipe do Project Zero no Google fornece outro exemplo de como a injeção de destino de ramificação pode ser usada para criar uma exploração em funcionamento.
fonte
Esta pergunta foi feita há um tempo e merece uma resposta mais recente.
Sumário Executivo :
As sequências de "retpolina" são uma construção de software que permite que ramos indiretos sejam isolados da execução especulativa. Isso pode ser aplicado para proteger os binários confidenciais (como implementações de sistema operacional ou hipervisor) contra ataques de injeção do destino da ramificação contra suas ramificações indiretas.
A palavra " ret poline " é um portmanteau das palavras "return" e "trampoline", bem como a melhoria " rel poline " foi cunhada a partir de "call relativo" e "trampolim". É uma construção de trampolim construída usando operações de retorno que também garantem figurativamente que qualquer execução especulativa associada “salte” infinitamente.
O uso desta opção de compilador protege apenas contra o Spectre V2 nos processadores afetados que possuem a atualização de microcódigo necessária para CVE-2017-5715. Ele ' funcionará ' em qualquer código (não apenas em um kernel), mas apenas o código contendo "segredos" vale a pena atacar.
O compilador LLVM tem um
-mretpoline
comutador desde antes de 4 de janeiro de 2018 . Essa data é quando a vulnerabilidade foi relatada publicamente pela primeira vez . O GCC disponibilizou seus patches em 7 de janeiro de 2018.A data do CVE sugere que a vulnerabilidade foi ' descoberta ' em 2017, mas afeta alguns dos processadores fabricados nas últimas duas décadas (portanto, provavelmente foi descoberta há muito tempo).
Primeiro, algumas definições:
Trampolim - Às vezes chamados de vetores de salto indiretos, os trampolins são locais de memória contendo endereços que apontam para interromper rotinas de serviço, rotinas de E / S, etc. Tradicionalmente, o GCC oferece suporte a funções aninhadas criando um trampolim executável em tempo de execução quando o endereço de uma função aninhada é obtido. Esse é um pequeno pedaço de código que normalmente reside na pilha, no quadro da pilha da função que o contém. O trampolim carrega o registro de cadeia estática e pula para o endereço real da função aninhada.
Thunk - Uma thunk é uma sub-rotina usada para injetar um cálculo adicional em outra sub-rotina. Os thunks são usados principalmente para atrasar um cálculo até que seu resultado seja necessário ou para inserir operações no início ou no final da outra sub-rotina
Memoização - Uma função memorizada "lembra" os resultados correspondentes a algum conjunto de entradas específicas. As chamadas subseqüentes com entradas lembradas retornam o resultado lembrado em vez de recalculá-lo, eliminando, assim, o custo primário de uma chamada com determinados parâmetros de todas, exceto a primeira chamada feita à função com esses parâmetros.
Muito grosso modo, um retpolim é um trampolim com um retorno como um thunk , para " estragar " a memorização no preditor indireto de ramificação.
Fonte : O retpoline inclui uma instrução PAUSE para Intel, mas uma instrução LFENCE é necessária para a AMD, pois nesse processador a instrução PAUSE não é uma instrução de serialização; portanto, o loop de pausa / jmp usará excesso de energia, pois é especulado durante a espera de retorno imprevisível para o alvo correto.
A Arstechnica tem uma explicação simples do problema:
Do artigo da Intel: " Retpoline: uma mitigação de injeção de alvo de ramificação " ( .PDF ):
fonte