2 pontos por GN⁺ 2023-11-15 | 1 comentários | Compartilhar no WhatsApp
  • Em alguns processadores Intel, quando rep movs e um prefixo rex.r duplicado interagem com a otimização FSRM, a CPU pode entrar em um estado de “glitch” que foge das regras normais
  • A causa está na decodificação flexível de prefixos do x86 e no fato de que, em instruções que usam operandos implícitos, como movsb, um prefixo rex que originalmente deveria não ter significado acaba seguindo um determinado caminho de otimização
  • Em agosto de 2023, o pipeline de validação do Google encontrou resultados imprevisíveis nessa combinação e observou desvios inesperados, desvios incondicionais ignorados e anomalias no registro do ponteiro de instrução em xsave e call
  • Se vários núcleos acionarem o mesmo bug ao mesmo tempo, podem ocorrer uma machine check exception e a parada do sistema; como isso também é reproduzível dentro de uma VM convidada sem privilégios, é importante para ambientes de nuvem
  • A Intel publicou uma atualização de microcódigo para os processadores afetados; quando a atualização não é possível, é possível desativar fast strings em IA32_MISC_ENABLE, mas isso traz uma grande queda de desempenho

Prefixos x86 e rep movsb

  • rep movsb é uma forma idiomática de mover memória em x86: ao definir origem, destino, direção e contador, o processador cuida da cópia repetida
  • A instrução real é movsb, e rep é um prefixo que altera essa instrução para que ela seja repetida várias vezes
  • A decodificação de instruções x86 é relativamente flexível, então prefixos sem significado ou conflitantes geralmente são ignorados
    • Compiladores podem usar esses prefixos redundantes para preencher uma única instrução até o limite de alinhamento desejado
  • rex, vex e evex são prefixos que alteram a forma como a instrução seguinte é decodificada

A combinação problemática com o prefixo rex

  • O i386 tinha 8 registradores de uso geral, então era possível especificar registradores com 3 bits, mas o x86-64 tem 16 registradores de uso geral e precisa de bits adicionais
  • O prefixo rex fornece bits extras que a instrução seguinte pode tomar emprestados ao codificar operandos
    • Normalmente é escrito como rex.rxb, com os bits b, x, r e w definidos opcionalmente
  • Como movsb não explicita operandos na instrução e todos eles são implícitos, os bits rex em rex.rxb rep movsb não deveriam ter significado
  • Em geral, o processador simplesmente ignora esse prefixo rex, mas em sistemas com suporte a fast short repeat move essa combinação leva a uma vulnerabilidade

FSRM e processadores afetados

  • FSRM é um recurso introduzido no Ice Lake que complementa a limitação do ERMS no tratamento de strings curtas
  • ERMS (enhanced repeat move/store) pode tornar o código rep movsb existente mais rápido ao tratar o alinhamento do buffer e stores largos no microcódigo
    • Como o custo inicial de configuração é alto, ele não é adequado para strings muito curtas
  • FSRM é um recurso para processar mais rapidamente movimentações curtas de até 128 bytes
  • É possível verificar o suporte pela flag fsrm na linha flags de /proc/cpuinfo
  • Exemplos de processadores que incluem FSRM:
    • Ice Lake
    • Rocket Lake
    • Tiger Lake
    • Raptor Lake
    • Alder Lake
    • Sapphire Rapids
  • Esta lista não é abrangente; para a lista completa, consulte o advisory da Intel INTEL-SA-00950

Descoberta e reprodução

  • O pipeline de validação do Google executa duas formas de programas gerados aleatoriamente usando a técnica de Oracle Serialization e depois compara se o estado final é o mesmo
  • Em agosto de 2023, resultados imprevisíveis ocorreram ao adicionar um prefixo rex.r duplicado a rep movs otimizado por FSRM
  • Os comportamentos anômalos observados foram:
    • Desvio para um local inesperado
    • Desvio incondicional ignorado
    • Ponteiro de instrução (instruction pointer) não registrado corretamente em instruções xsave ou call
    • Depurador relatando um estado impossível
  • Quando vários núcleos acionam o mesmo bug, o processador pode relatar uma machine check exception e parar
  • Como é reproduzível também dentro de uma VM convidada sem privilégios, isso se torna um problema de segurança importante para provedores de nuvem
  • A ferramenta de reprodução e o material de pesquisa foram publicados no repositório de pesquisas de segurança do Google
    • Um espelho local da ferramenta icebreak também é fornecido como icebreak.tar.gz
  • icebreak tenta reproduzir o problema especificando diferentes pares de núcleos
    • Em sistemas não afetados, não deve haver saída, como em um loop infinito
    • Em sistemas afetados, um . é exibido a cada reprodução bem-sucedida
    • Em núcleos irmãos SMT, podem ser observados desvios aleatórios
    • Em núcleos irmãos SMP do mesmo pacote, pode ser observado um machine check
    • Se dois núcleos diferentes não forem especificados, pode ser necessária uma hammer thread

Possível causa e impactos observados

  • Como o comportamento do microcódigo em sistemas modernos não é público, a causa raiz só pode ser tratada como uma teoria baseada em observações
  • A CPU se divide, em termos gerais, em front-end e back-end
    • O front-end busca e decodifica instruções, gerando μops
    • O back-end executa instruções fora de ordem e armazena e finaliza os resultados no ROB (reorder buffer)
  • Este bug parece fazer o front-end calcular incorretamente o tamanho da instrução movsb, levando a um estado em que entradas posteriores do ROB são associadas a endereços incorretos
  • Nesse estado, ocorre uma confusão em que o ponteiro de instrução é calculado de forma errada
  • O sistema pode acabar se recuperando internamente para um estado consistente, mas resultados intermediários podem estar incorretos
  • Se vários núcleos SMT ou SMP entram nesse estado simultaneamente, pode ocorrer dano suficiente ao estado microarquitetural para forçar um machine check
  • É possível corromper o estado do sistema a ponto de provocar um machine check, e foi observada interferência entre threads na execução de processos agendados em núcleos irmãos SMT
  • Não foi confirmado se a corrupção pode ser controlada com precisão suficiente para escalonamento de privilégios

Mitigações

  • A Intel publicou microcódigo atualizado para todos os processadores afetados em INTEL-SA-00950
  • O sistema operacional ou o fornecedor do BIOS talvez já tenham disponibilizado a atualização
  • Caso não seja possível atualizar, é possível desativar fast strings por meio do registrador específico de modelo IA32_MISC_ENABLE
  • Desativar fast strings causa uma grande queda de desempenho, portanto não deve ser usado a menos que seja realmente necessário

Materiais relacionados sobre bugs de CPU

  • O Google vem divulgando bugs de CPU que encontrou, e alguns valem a leitura mesmo quando não têm impacto de segurança
  • Exemplos de materiais:

1 comentários

 
GN⁺ 2023-11-15
Opiniões no Hacker News
  • Artigo relacionado: https://cloud.google.com/blog/products/identity-security/goo...
    Conteúdo vindo de https://news.ycombinator.com/item?id=38268043, mas os comentários foram consolidados aqui

  • Ao ler este artigo, percebi o quanto sei pouco sobre o hardware em que meu software roda
    Ele diz que “prefixos permitem ativar ou desativar funcionalidades para mudar o comportamento das instruções”; fico curioso por que seria necessário um “prefixo” para ligar e desligar funcionalidades
    Seria para alternar recursos dinamicamente sem entrar no BIOS?

    • Vale ler https://wiki.osdev.org/X86-64_Instruction_Encoding#Legacy_Pr...
      O prefixo REP é o mais comum e serve para fazer a mesma instrução se repetir um número variável de vezes
      A contagem de repetições vem do registrador CX, o que torna muito curtos loops comuns, como mover objetos na memória
      A função memcpy muitas vezes é inlineada como uma única instrução REP MOVS, acompanhada, se necessário, de uma instrução que copia a contagem para CX
      O prefixo REX também é bastante comum, porque programas de 64 bits lidam frequentemente com valores e endereços de 64 bits
      Nenhum prefixo alterna algo configurável globalmente via BIOS ou afins; todos apenas especificam o que a próxima instrução deve fazer
    • Nesse caso, “prefixo” em geral serve para expandir o espaço de codificação de instruções
      Modos de endereçamento raramente usados levam um “prefixo de segmento” que faz usar um segmento diferente de DS, e o prefixo “REX” do x86_64 adicionou bits aos campos de registradores para permitir usar 16 registradores de uso geral
      Da mesma forma, embora a especificação original fosse ruim, o prefixo “LOCK” torna algumas operações de memória atômicas em relação ao restante do sistema, como ao implementar compare-and-set com “LOCK CMPXCHG”
      Outras arquiteturas de CPU também expressam essas operações, mas geralmente as colocam dentro do espaço de instruções existente, o que exige mais bits para representar todas as instruções
      O prefixo “REP” que está em questão aqui é uma espécie de exceção: é um prefixo de repetição em microcódigo que sobrou dos tempos antigos
      Ainda assim, ele representa operações sensíveis a desempenho até hoje, como memset/memmove, então vale a pena para fabricantes de CPU continuarem otimizando isso, e este bug parece ter surgido nesse processo
    • Prefixos são modificadores de uma instrução específica executada pelo processador, usados, por exemplo, para controlar o tamanho dos operandos ou ativar travas para concorrência
    • O x86 foi projetado em 1978, essencialmente para rodar impressoras a laser primitivas ou tarefas semelhantes
      O maior problema é que ele “usou de forma eficiente” o espaço de codificação das instruções
      Quando depois surgiram novas instruções e, pior, registradores adicionais, foi preciso encaixar de algum jeito novas variantes de instruções, e a solução foi acrescentar prefixos
    • O x86, como arquitetura de conjunto de instruções, vem recebendo acréscimos há mais de 40 anos e usa instruções de comprimento variável, por isso acabou ficando assim
      A cada expansão do conjunto de instruções, escavaram uma parte do espaço de opcodes para enfiar um novo prefixo
      Pelo fato de a Intel ter proposto ainda outro método novo este ano, parece que essa tendência vai continuar
  • O processo de diagnóstico me lembrou do que aconteceu quando o qemu encontrou repz ret: https://repzret.org/p/repzret/

  • Acho que, pelas regras do HN, títulos assim deveriam ser proibidos
    Ele não diz absolutamente nada sobre o que é o link, e a URL só confunde ainda mais
    Se o título é tão sem sentido, acho que quem postou deveria acrescentar uma breve explicação

    • Discordo
      Já vi que, quando o título traz o máximo de contexto possível, as pessoas não clicam no link e passam a polir nos comentários apenas seus próprios interesses, como se estivessem reagindo a um tweet
      O HN escolhe um meio-termo que incentiva a curiosidade intelectual e o clique no link
      Mesmo que alguém se recuse a clicar por causa de um título ambíguo, pelo menos vai responder às pessoas que clicaram, o que considero melhor do que em outros lugares da internet
      Posts sem recompensa suficiente para justificar um título ambíguo e espirituoso, ao contrário deste, saem da primeira página
  • O texto é muito bem escrito
    Quase não entendo de programação em assembly, nem do conjunto de instruções da Intel, e muito menos de microarquitetura, mas consegui acompanhar a explicação e sinto que entendi em linhas gerais o que está acontecendo aqui
    Fico curioso se alguém sabe se CPUs da AMD também são afetadas

  • Se o problema é realmente o processador se confundir com o comprimento da instrução, é impressionante que isso possa ser corrigido em microcódigo sem uma grande perda de desempenho
    Meu instinto pode estar completamente errado, mas eu imaginaria que o cálculo do comprimento da instrução fosse algo sintetizado diretamente em portas lógicas
    Pensando de novo, talvez o decodificador de uOPs esteja perfeitamente correto em hardware, e uma rotina de cópia otimizada em microcódigo esteja tentando inferir algo falso sobre o fluxo de uOPs
    Por exemplo, algo como “ah, isto é rep mov, então basta voltar duas uOPs para fazer o loop”
    Imagino que a equipe de CPUs da Intel não vá divulgar detalhes tão específicos

  • Não conheço bem “ERMS” e “FSRM”, e quase não parece haver bons materiais no Google
    Fico me perguntando se eles são apenas flags CPUID que indicam que rep movsb pode ser usado com desempenho máximo em vez de uma implementação SSE otimizada de memcpy, ou se são alguma codificação ou prefixo especial que torna rep movsb mais rápido
    Se for o segundo caso, não sei por que seriam necessários nem como fsrm é utilizado

    • Encontrei este material [1], e o manual de otimização da Intel [2] também está linkado
      ERMS parece ter sido uma alternativa mais barata ao AVX, e FSRM, uma versão melhor para blocos curtos
      “As versões de baixo custo de processadores posteriores, os Kaby Lake Celeron e Pentium lançados em 2017, não têm AVX, que pode ser usado para cópia rápida de memória, mas têm Enhanced REP MOVSB
      E algumas arquiteturas móveis e de baixo consumo da Intel lançadas a partir de 2018 não eram baseadas em SkyLake, mas copiavam cerca de duas vezes mais bytes por ciclo de CPU com REP MOVSB do que as microarquiteturas da geração anterior”
      “Antes do Fast Short REP MOV(FSRM) da microarquitetura Ice Lake, o Enhanced REP MOVSB(ERMSB) só era mais rápido que cópias AVX ou cópias com registradores de uso geral quando o tamanho do bloco era de pelo menos 256 bytes
      Em blocos menores que 64 bytes, o custo interno de inicialização do ERMSB era alto, cerca de 35 ciclos, tornando-o muito mais lento; o objetivo do recurso FSRM era tornar também rápidos os blocos menores que 128 bytes”
      [1] https://stackoverflow.com/a/43837564
      [2] http://www.intel.com/content/dam/www/public/us/en/documents/...
    • FSRM é apenas o nome de uma otimização de CPU que afeta código existente
      A escolha e o escalonamento ideais de instruções podem ser feitos estaticamente em tempo de compilação, ou dinamicamente em tempo de execução, escolhendo uma entre várias funções de biblioteca ou usando JIT
      Para detectar em tempo de execução qual escalonamento de instruções é o ideal, é preciso conhecer a CPU real
      Seria possível manter uma tabela com todos os modelos de CPU, mas também é possível perguntar ao sistema operacional se a CPU em execução implementa aquela otimização
      O Linux precisou de um patch para permitir informar que a CPU implementava essa otimização
      https://www.phoronix.com/news/Intel-5.6-FSRM-Memmove
    • A flag apenas informa que, nesta CPU, rep movsb é rápido, portanto não é necessário usar uma implementação otimizada com SSE/AVX
  • Vi que o comunicado da Intel [1] dizia o seguinte
    A Intel agradeceu aos funcionários da Intel que descobriram internamente esse problema, e também agradeceu aos funcionários do Google que relataram o problema
    [1] https://www.intel.com/content/www/us/en/security-center/advi...

    • Fico curioso sobre quanto antes os funcionários da Intel descobriram esse problema em relação ao Google
  • O comunicado da Intel com a descrição do impacto também merece referência: https://www.intel.com/content/www/us/en/security-center/advi...
    “Em alguns processadores Intel(R), uma sequência de instruções do processador pode causar comportamento inesperado, permitindo que um usuário autenticado, por meio de acesso local, possibilite elevação de privilégio, divulgação de informações ou negação de serviço”

    • Aqui, “alguns” parece significar quase todos os CPUs Intel x86 fabricados nos últimos 6 anos
  • Konrad Magnusson, da equipe de Victoria 3 da Paradox Interactive, encontrou algo relacionado a isso e ao mimalloc: https://github.com/microsoft/mimalloc/issues/807
    Não sei se é totalmente relacionado, mas é possível

    • Se não tiver emitido de alguma forma um prefixo desnecessário, parece pouco provável que esteja relacionado