1 pontos por GN⁺ 3 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • O binário x86_64-unknown-linux-musl do Ripgrep 15.2.0 encerra intermitentemente com SIGSEGV ao pesquisar árvores de arquivos grandes com alta concorrência
  • A falha ocorre dentro de calloc, chamado por opendir, e o ponto de verificação de integridade dos metadados de heap do musl mallocng aparece no topo do rastreamento de pilha
  • O ambiente de reprodução é uma árvore com cerca de 20 GiB e 1,8 milhão de arquivos, repetindo buscas com rg por uma string inexistente
  • Em um sistema de 24 núcleos, se houver RAM suficiente para que a árvore de busca caiba no cache de blocos do kernel, o problema geralmente aparece em cerca de 1 minuto
  • O problema foi reproduzido de forma independente não só no rg incluído no OpenAI Codex, mas também em um binário idêntico byte a byte ao release oficial, confirmando que se trata de um problema não relacionado às dependências do Codex

Ambiente de ocorrência

  • A versão usada é ripgrep 15.2.0 rev e89fff8, com o recurso +pcre2
    • SIMD na compilação: +SSE2,-SSSE3,-AVX2
    • SIMD em execução: +SSE2,+SSSE3,+AVX2
    • PCRE2 10.45 e JIT disponíveis
  • O sistema operacional é OpenSUSE Tumbleweed Linux x86_64
  • O rg empacotado no OpenAI Codex, onde o problema foi visto pela primeira vez, é idêntico byte a byte ao release oficial x86_64-unknown-linux-musl
  • Separadamente do Codex, o problema também foi reproduzido no binário oficial, e o binário usado para análise foi compilado com símbolos de depuração usando o seguinte comando
    • CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl

Passos para reproduzir

  • generate_repro_tree.py gera uma árvore aleatória de arquivos imitando as estatísticas do repositório original onde o problema ocorreu
    • Esse programa foi escrito por um LLM
    • O resultado gerado tem cerca de 20 GiB e 1,8 milhão de arquivos
  • Na raiz da árvore gerada, repete-se a busca por uma string aleatória inexistente
    • while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
  • Foi observado que uma árvore de busca suficientemente grande é essencial para reproduzir o problema
  • Em um sistema de 24 núcleos, se houver RAM livre suficiente para que toda a árvore caiba no cache de blocos do kernel, a falha geralmente acontece após cerca de 1 minuto

Ponto da falha

  • O resultado real é um SIGSEGV que deixa core dump
  • O topo do rastreamento de pilha é get_meta do musl mallocng, e a falha ocorre no ponto de verificação de integridade dos metadados de heap
  • O fluxo de chamadas segue de opendir para calloc, depois para a travessia de diretórios da biblioteca padrão do Rust e para o worker ignore::walk do ripgrep
    • get_meta__malloc_allzeropcallocopendir
    • std::fs::read_dirignore::walk::Work::read_dirignore::walk::Worker::run
  • Como material de análise, foram anexados um core dump e o binário rg correspondente

Comportamento esperado e estado atual

  • O comportamento esperado é executar sem falha de segmentação, mesmo em buscas grandes e altamente concorrentes
  • O conteúdo fornecido não inclui confirmação da causa, correção proposta, resultado da análise ou status final de resolução

1 comentários

 
GN⁺ 3 시간 전
Opiniões no Hacker News
  • Há um trecho interessante no patch do kernel: https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...

    vi um relatório de bug interessante no ripgrep e uma análise gerada por IA, diligente, mas bem ruim
    Isso se refere a https://github.com/dfoxfranke/ripgrep-3494-analysis, e eu também achei longo demais para parecer escrito por uma pessoa. Além disso, esse thread parece ter sido publicado hoje mesmo.

    • Foi uma leitura penosa, e não encontrei, em nenhum ponto daquele texto prolixo, algo que apontasse para o mesmo código ou área que a pessoa de verdade no lore.kernel encontrou. Queria perguntar a alguém que entenda melhor o Claude: existe mesmo algum trecho que identifique a causa real?
    • Até por volta de 2000, quem usava celular em público era tratado como um sujeito metido e desagradável; a IA agora está passando por uma fase parecida de rejeição incômoda.
      Dois anos atrás, uma análise dessas teria sido vista como o resultado de alguém doando generosamente seu tempo à comunidade, mas agora, sabendo a origem e que o custo em tokens foi de apenas US$ 0,06, ninguém quer ler. O fato de ela prenunciar o que vem pela frente também contribui.
      Daqui a alguns anos, investigar bugs manualmente será o último recurso, e outros agentes de IA lerão os relatórios para validar as correções. Assim como ignoramos assembly gerado por compilador e usuários de celular em público, parece que vamos passar da fase de zombaria para a fase de indiferença.
  • Entendo usar o alocador padrão do musl por conveniência, mas é estranho não trocar por um alocador mais rápido em uma aplicação cujo propósito é justamente velocidade.
    O mallocng sofre com contenção em múltiplas threads. Uma aplicação que normalmente era limitada por E/S passou a ter gargalo em malloc com apenas 8 threads quando compilada com musl; ao trocar para mimalloc, o desempenho ficou 20 vezes melhor, chegando muito perto da configuração padrão com glibc, embora ainda um pouco mais lento que glibc+mimalloc.
    De fato há um problema interessante aí, mas ele nem deveria ter aparecido dessa forma.

    • O ripgrep, quando compilado para musl de 64 bits, na verdade define jemalloc como alocador global: https://github.com/BurntSushi/ripgrep/blob/435f59fc4b43af3ab...
    • Pelo stack trace da falha de segmentação, a alocação ocorre em opendir, da libc musl. A forma como o Rust substitui o alocador usado por ele não troca o alocador do processo inteiro, apenas o alocador chamado pelo código Rust.
      Ainda assim, se for preciso passar por um alocador que usa lock global, parece melhor o ripgrep evitar usar opendir da libc.
    • Isto é um bug do kernel. Concordo que os alocadores da libc costumam ser ruins sem grande motivo, mas este problema parece poder ocorrer com probabilidade semelhante em outro código de aplicação, incluindo mimalloc ou glibc.
    • A maioria dos programas melhora a velocidade reutilizando alocações. Se não houver alocação, nem é preciso um alocador rápido, e o trabalho do ripgrep em si não exige, por natureza, alocações frequentes.
    • Na verdade, foram os recursos de hardening oferecidos pelo mallocng do musl que permitiram descobrir o bug do kernel. Caso contrário, ele poderia ter passado meses corrompendo memória silenciosamente sem chamar atenção.
  • Se você está rodando ripgrep em um cluster HPC contra um sistema de arquivos de cluster em grande escala, pare imediatamente e redesenhe o fluxo de trabalho. Esse tipo de tarefa gera uma enorme quantidade de pequenas operações de E/S, que são o calcanhar de Aquiles de sistemas de arquivos de cluster em grande escala.
    Você acaba empurrando para a camada de metadados do sistema de arquivos um trabalho que deveria ser feito na hierarquia de memória de alta largura de banda do cluster; bastam alguns usuários executando isso ao mesmo tempo para paralisar todo o sistema de arquivos de alta largura de banda.

    • Isto não é um cluster HPC, é apenas btrfs na minha workstation.
    • Fiquei me perguntando se a causa raiz da instabilidade recente do GitHub não seria algo parecido. De repente há bilhões de operações com arquivos pequenos, amplificadas pelo uso de IA, e o grafo de objetos é inerentemente fragmentado, dificultando até trazer uma página antecipadamente e fazer operações comuns do Git tocarem apenas nos objetos dentro dela.
      Se houver qualquer verdade em https://isolveproblems.substack.com/p/how-microsoft-vaporize..., basta um único caminho não otimizado na abstração de sistema de arquivos do Azure para que um pico de uso se transforme em um raio de impacto enorme.
  • Talvez fosse melhor linkar diretamente a análise do bug do kernel: https://github.com/dfoxfranke/ripgrep-3494-analysis

    • Tentei ler, mas desisti no primeiro parágrafo de Headline.
      Ele vem com frases como: “um valor armazenado por uma thread em uma página anônima recém-paginada desaparece ao ser relido pela mesma thread cerca de 10 instruções depois, e o backing da página é trocado durante a execução da função”, “ao ler o pagemap no momento da falha, o backing é a zero page do kernel”, “o mecanismo é localizado na interação entre o caminho rápido de falha anônima com lock por VMA e o TLB shootdown de um munmap concorrente”.
      É difícil entender o que é o backing de uma página, o que significam freshly-faulted ou “cerca de 10 instruções depois”, ou como um mecanismo é “localizado”. Parece mais um amontoado de palavras do que uma explicação técnica.
      Um exemplo de documentação técnica bem-feita é este: https://yifan.lu/2019/01/11/the-first-f00d-exploit/
    • A parte “overflows continuam overflowando, use-after-free continua sendo use-after-free, e a disputa da máscara do musl continua disputando” parece poesia de computador, achei engraçado.
      A conclusão parece ser, grosso modo, que há um problema na combinação Linux 7.0 e musl 1.2.5, mas a reprodução ainda só funciona intermitentemente, sob carga pesada, no mesmo CPU Threadripper físico, então não dá para descartar problema de hardware.
    • Ler um relatório de bug gerado por IA é horrível.
    • O rastreamento em si é ótimo, mas a explicação não faz sentido. Um flush adicional da TLB não pode ser erro; a CPU pode fazer flush quando quiser. O erro real parece ser a existência de uma PTE de zero page quando ela não deveria existir.
      Parece uma condição de corrida complexa causada por uma migração de CPU em um momento estranho, ou um bug em que o caminho de remoção da tabela de páginas expôs temporariamente uma PTE incorreta. Também não acho que o PFN da zero page seja 0.
      Se eu tivesse que chutar, diria que, durante a remoção direta da tabela de páginas, foi permitido que a CPU lesse uma tabela já liberada e reutilizada por meio de uma entrada em cache de uma estrutura de paginação superior. Já depurei esse tipo de problema antes, e é horrível.
    • É a típica análise verborrágica de resíduo de LLM. Pode até estar correta, mas é difícil ler com atenção; se uma pessoa tivesse feito a mesma análise, teria reduzido o texto para um quinto do tamanho.
  • Por que o bug acontece só na musl libc e não em outras libc?

    • É apenas coincidência, e inclusive acontece só em uma máquina.
    • Provavelmente porque o alocador do musl expõe diretamente à aplicação uma única página que acabou de sofrer page fault. Outros alocadores normalmente pré-alocam várias páginas de uma vez, então a janela de tempo da condição de corrida fica mais estreita.
  • Normalmente eu suspeitaria do tamanho da stack das threads no musl, mas fiquei curioso para saber se isso foi confirmado como bug do kernel.