Após o Meltdown e o Specter, que foram divulgados publicamente em janeiro, em seguida as vulnerabilidades do Spectre V3a e V4 ocorreram em maio, e esta última pode ser parte das 8 novas vulnerabilidades no total que serão divulgadas no decorrer do ano.
No início deste ano, Julian Stecklina (Amazon) e Thomas Prescher (Cyberus Technology) descobriram em conjunto e divulgaram com responsabilidade outra vulnerabilidade e chamaram de LazyFP . O LazyFP (CVE-2018-3665) é um ataque direcionado a sistemas operacionais que usam comutação lenta de FPU. Este artigo descreve o que esse ataque significa, descreve como ele pode ser atenuado e como ele realmente funciona.
Resumo e Implicações
A divulgação pública desta vulnerabilidade foi inicialmente adiada por um embargo típico de informações de divulgação responsável até agosto, mas os primeiros rumores levaram a que essa data fosse descartada.
O estado do registro da unidade de ponto flutuante (FPU), que consiste nos conjuntos de registros AVX, MMX e SSE, pode ser vazado nos limites do domínio de proteção. Isso inclui vazamentos nos limites de processos e máquinas virtuais.
O estado da FPU pode conter informações confidenciais, como chaves criptográficas. Como exemplo, o conjunto de instruções Intel AES (AES-NI) usa registradores FPU para armazenar chaves redondas. Só é possível explorar quando o sistema operacional ou hypervisor subjacente usa a comutação de FPU lenta.
Outro caso de uso interessante dessa vulnerabilidade é a criação de canais ocultos de alto desempenho entre as VMs. Ao usar todos os conjuntos de registros e controlar a frequência de programação do núcleo, é possível maximizar a taxa de transferência até o intervalo de MB / s.
Os usuários são afetados quando executam uma combinação de processador afetado e sistemas operacionais afetados.
- Sistemas Operacionais Afetados:
- Linux:
- versões do kernel <4.9 com parâmetros de inicialização não padrão (
eagerfpu=off
) são afetados - versões do kernel <4.6 rodando nos processadores Intel afetados antes do Haswell ou com parâmetros de inicialização personalizados (
eagerfpu=off
ounoxsave
) - versões do kernel 4.4.y com y <138 tem um bug em sua ansiosa troca de FPU
- versões do kernel <3.7 em todas as CPUs afetadas
- As versões do kernel Linux começando com 4.9 não são afetadas, independentemente da CPU.
- versões do kernel <4.9 com parâmetros de inicialização não padrão (
- FreeBSD
- Microsoft Windows, consulte o aviso ADV180016
- Linux:
- Hipervisores afetados:
- KVM quando executado em versões de kernel do Linux afetadas
- Todas as versões do Xen e geralmente todos os hipervisores que empregam comutação preguiçosa do FPU
- CPUs afetadas quando o sistema operacional afetado ou o hipervisor é usado:
- Verificamos o problema na microarquitetura Intel Core de Sandy Bridge para Skylake
- Estado de outros processadores não é claro neste momento
A atenuação requer que o software do sistema use a comutação ansiosa FPU em vez da comutação lenta do FPU.
Antecedentes Técnicos
Esta vulnerabilidade é semelhante ao Meltdown (CVE-2017-5754) . Enquanto o Meltdown permitia ler o conteúdo da memória protegida de um programa de espaço do usuário, esse novo ataque permite ler determinados conteúdos de registro nos limites do domínio de proteção.
Para explorar a vulnerabilidade, o invasor acessa os registros FPU que ele não tem permissão para acessar ainda. Esse valor proibido é alimentado em um cálculo de endereço que é usado para um acesso de memória subseqüente. A CPU executará esta série de instruções especulativamente, ou seja, o conteúdo do registro correspondente é esmagado quando detecta seu erro.
Em seguida, ele tenta ler / gravar de / para um endereço de memória que contém o valor de registro proibido como parte de seu deslocamento de endereço. A CPU executará especulativamente essa série de cargas e armazenamentos, mas detectará e corrigirá seu erro.No entanto, essa execução especulativa deixa rastros no cache que podem vazar informações ocultas para o invasor.
Para entender melhor como esse ataque realmente funciona, é necessário aprofundar o funcionamento interno do x86 FPU e como ele é usado pelos sistemas operacionais.
A unidade de ponto flutuante (FPU)
Nos primórdios do x86, o FPU (também chamado de coprocessador matemático) era um coprocessador externo que poderia ser adicionado à arquitetura de processador x86 adotada agora pela Intel. O Intel 8087 foi o primeiro co-processador matemático de ponto flutuante deste tipo. O objetivo dessa extensão era acelerar as operações matemáticas em números de ponto flutuante, como divisão e multiplicação. Com o modelo de CPU Intel 486DX (lançado em 1989), o FPU foi integrado ao próprio microchip. Dessa forma, nenhum coprocessador adicional era necessário.
Ao longo dos anos, o processador foi estendido para suportar conjuntos de instruções de instrução única, dados múltiplos (SIMD), ou seja, MMX / SSE / AVX. As instruções SIMD executam a mesma operação matemática em vários pares de operandos ao mesmo tempo para melhorar o desempenho. Cada uma dessas extensões de conjunto de instruções introduziu novos conjuntos de registradores que continuam sendo gerenciados como parte do estado do registro da FPU. Em processadores Intel recentes, o estado do registrador FPU pode conter mais de 2 kB de dados (o AVX2 oferece 32 registradores de 512 bits cada, o que se traduz em um estado de processador adicional de 2 kB). Devido à utilidade destas instruções, este conjunto de registros pode conter não apenas valores de ponto flutuante, mas também outros dados como, por exemplo, valores inteiros.
Carregando e armazenando o estado do FPU
Para habilitar a multitarefa, os sistemas operacionais interrompem periodicamente os processos para dar a outros processos a chance de serem executados. Caso contrário, um único processo que faz um loop para sempre poderia parar o sistema.
Quando o sistema operacional alterna de um processo para outro, ele precisa salvar o estado do processo anterior e restaurar o estado do processo que está prestes a ser executado. Este estado consiste nos valores armazenados em registradores de propósito geral e registros FPU.
A arquitetura de instruções x86 fornece várias instruções para carregar / armazenar o estado do registrador da FPU de / para a memória. Já sabemos sobre o tamanho grande do estado da FPU, portanto, é bastante óbvio que não se deseja ler e gravar tais quantidades de dados em todas as mudanças de contexto, a menos que seja realmente necessário, porque nem todos os processos usam a FPU.
Comutação FPU ansiosa e lenta
O comutador FPU ansioso é comparável a salvar o estado de registrador de propósito geral em um comutador de contexto. Para cada processo, o sistema operacional reserva uma área na memória para salvar o estado da FPU. Ao alternar de um processo para outro, ele executa uma instrução de armazenamento de FPU para transferir o conteúdo da FPU atual para a área de salvamento do estado. Em seguida, ele carrega o novo estado FPU da área de salvamento do estado do processo que está prestes a ser planejado.
A comutação FPU preguiçosa otimiza este procedimento para o caso em que nem todo processo usa o FPU o tempo todo.
Após uma troca de contexto, a FPU é desativada até ser usada pela primeira vez. Só então, o antigo estado FPU será salvo e o correto será restaurado da memória. Até este ponto, o FPU mantém o estado do registro do processo ou da VM que o usou por último.
Para implementar essa otimização, o kernel do sistema operacional desabilita temporariamente o FPU definindo um certo bit de registro de controle ( CR0.TS
). Se este bit estiver definido, qualquer tentativa de acessar o FPU causará uma exceção ( #NM
, Dispositivo não disponível, Nenhum coprocessador matemático ). Quando a exceção ocorre, o kernel pode salvar o estado antigo na respectiva área de estado e restaurar o estado do processo atual. Os processos que simplesmente não usam o FPU nunca acionam essa exceção e, portanto, nunca acionam armazenamentos e cargas de estado do FPU.
Começando com a introdução da arquitetura x86-64 , a presença de pelo menos algumas extensões de conjuntos de instruções SIMD é obrigatória e seu uso se tornou mais difundido.Como tal, a suposição subjacente de comutação de FPU preguiçosa não é mais válida. O ganho de desempenho pela comutação preguiçosa do FPU tornou-se insignificante, e alguns núcleos já o removeram em favor de troca rápida.
O ataque
O ataque funciona lendo o conteúdo do registro FPU enquanto o FPU está desabilitado e divulgando o conteúdo através de um canal lateral baseado em cache. Neste ponto, o FPU contém o conteúdo do registro do último processo que o utilizou.
Para tornar esse ataque prático, é necessário ocultar a exceção que normalmente ocorre ao acessar o FPU. Encontramos três maneiras diferentes de fazer isso:
Executando o ataque na sombra de um pagefault
Todas as três versões funcionam de forma semelhante. Uma versão simplificada do pagefault poderia ser assim:
mov dword [0], 0 ; Aciona um pagefault
movq rax, xmm0 ; Lê o conteúdo de xmm0, acionando #NM
and rax, 1 ; máscara bit 0
shl rax, 6 ; alinhando à linha de cache
mov dword [mem + rax], 0 ; acessando "mem" com offset dependendo do conteúdo de xmm0
O ataque
O truque aqui é que a exceção #NM
nunca ocorre, porque a instrução que toca no registrador FPU xmm0
é executada apenas especulativamente. No entanto, o local da memória tocado depende do valor de xmm0
e esse valor pode ser recuperado usando o mesmo ataque flush-and-reload que também é usado no Meltdown e no Specter. mem
é apenas um buffer de memória comum que é acessível ao aplicativo. Neste ponto, estamos de volta ao estado inicial e podemos tentar novamente o ataque para vazar todo o estado do registro, bit por bit (sempre mascarando um bit diferente na etapa and
do snippet de código).
OBS: Note que o código de exemplo neste artigo apenas lê um único bit por uma questão de simplicidade. Mas a prova de conceito dos descobridores da vulnerabilidade lê vários bytes ao mesmo tempo, a fim de reduzir ainda mais o tamanho da janela de tempo necessária.
Existem outros 2 métodos de ataque que a pedido da Intel não foram publicados pelos descobridores da falha.
Praticabilidade
A praticabilidade do ataque varia de acordo com os diferentes métodos para empregá-lo: O atacante precisa de uma janela de tempo em que nenhuma interrupção pelo sistema operacional ocorra para ler todos os bits do registro, porque toda preempção tem a chance de alterar o estado do registrador e tornar o resultado inútil. Assim, quanto mais rápido o método de ataque, maior a probabilidade de sucesso.
Mitigação
Usando servidores Linux com versões de kernel> = 3.7, este ataque pode ser mitigado adicionando “ eagerfpu=on
” aos parâmetros de inicialização do kernel. Não temos conhecimento de uma solução alternativa para versões mais antigas. Para todos os outros sistemas operacionais: instale as atualizações oficiais do fornecedor.
Não estamos cientes de uma penalidade de desempenho causada pelo uso de uma rápida troca de FPU. A mensagem de commit do patch do kernel do Linux que fez com que o FPU ansioso mudasse o padrão dos sistemas Linux até mesmo aponta que as suposições que justificaram a comutação lenta do FPU no passado não se sustentam mais na maioria dos sistemas modernos.
Pesquisa e tradução: Marcio Mariano Galvão – Team Linux Force.
Fonte: blog.cyberus-technology.de
0 responses on "Lazy FPU - A nova vulnerabilidade da Intel"