News · OpenAI atribui as falhas na pesquisa do ChatGPT a um servidor Azure com defeito e a uma race condition de 18 anos na libunwind
OpenAI atribui as falhas na pesquisa do ChatGPT a um servidor Azure com defeito e a uma race condition de 18 anos na libunwind
Como uma análise ao nível da população, feita sobre um ano de core dumps, separou dois bugs não relacionados que se disfarçavam de uma única falha impossível
Duas falhas com a mesma máscara
O tema é o Rockset, o sistema de pesquisa e análise nativo da cloud que a OpenAI adquiriu em 2024 e que agora usa para manter atualizado o índice da base de conhecimento de um workspace para o ChatGPT. A sua camada de execução está escrita em C++ e, poucos meses antes da publicação, começou a gerar falhas que pareciam fisicamente impossíveis: uma função normal parecia terminar e depois retornar para um endereço inválido, por vezes um slot de endereço de retorno a NULL, por vezes com o stack pointer %rsp desalinhado em exatamente 8 bytes.
Nenhum dos dois sintomas se explica por código de aplicação normal. Uma escrita descontrolada que atinja apenas um endereço de retorno guardado é extremamente improvável, e desalinhar o %rsp em 8 bytes sem assembly inline, setcontext ou longjmp — nenhum dos quais é usado pelo Rockset — é ainda mais estranho, porque o código compilado só toca esse registo no prólogo e no epílogo de uma função. Todas as hipóteses avançadas pela equipa ou pelo ChatGPT tinham fortes evidências contra si. A razão, acabou por se revelar, era que estavam a observar dois bugs não relacionados ao mesmo tempo: corrupção silenciosa de hardware num único servidor Azure e uma race condition de 18 anos na GNU libunwind.
Médico versus epidemiologista
O instinto inicial da equipa foi clínico: inspecionar de perto um punhado de core dumps, formular hipóteses e eliminá-las uma a uma. A maioria das falhas ocorria num único método fortemente inlined, DocumentTree::updateDocument, o que deixava um conjunto avassalador de funções candidatas. As queries aos logs produziam falsos positivos e falsos negativos, porque os bugs de corrupção da stack corrompiam precisamente os stack traces usados para os classificar. A inspeção manual não era escalável. Piorando as coisas, a equipa já tinha descartado uma causa de hardware porque as falhas surgiam em várias regiões e tipos de hardware — uma conclusão que estava exatamente invertida para uma das duas populações.
O ponto de viragem foi uma mudança de método. Em vez de diagnosticar um único paciente, trataram toda a frota como uma população. Pediram ao ChatGPT que escrevesse um script que descarregava um prefixo de cada core file, extraía os registos, filtrava falsos positivos conhecidos comparando com os logs, e classificava cada falha como retorno-a-null, stack desalinhada, ou outra. Ao executá-lo sobre todos os core dumps de produção do Rockset do ano anterior, as correlações surgiram de imediato: as falhas de retorno-a-null estavam dispersas por muitos clusters sem uma data de início clara, enquanto as falhas de stack desalinhada vinham de uma única região, tinham uma data de início bem definida e nunca ocorriam em nós de longa duração — a assinatura de uma única máquina física com hardware defeituoso.
Uma janela de cem picossegundos, e a estimativa de Fermi que a tornou credível
O servidor com defeito foi colocado em denylist, e as suas falhas de stack desalinhada desapareceram, apesar de a equipa nunca ter conseguido reproduzir a corrupção de registos em semanas de testes de stress. Removido esse cluster, os restantes core dumps de retorno-a-null ocorriam todos durante o unwinding de exceções em C++. A GNU libunwind sintetiza um ucontext_t na stack e entrega-o a uma rotina interna, _Ux86_64_setcontext, cujas instruções finais movem o %rsp para o novo fundo da stack antes de uma instrução posterior ler o instruction pointer de destino a partir dessa mesma struct. Depois de o %rsp se mover, a struct deixa de estar protegida pela red zone, pelo que um sinal entregue nesse intervalo permite ao kernel construir o seu signal frame sobre essa memória e zerar o instruction pointer restaurado.
A janela vulnerável é uma única instrução — cerca de cem picossegundos num CPU moderno fora de ordem — o que parecia demasiado estreito para explicar mais de uma dúzia de falhas de retorno-a-null por dia. Uma estimativa de Fermi fechou a lacuna: uma janela de 10⁻¹⁰ segundos face a um SIGUSR2 a chegar a cada 10⁻² segundos de tempo de CPU dá a cada handler de limpeza uma probabilidade de cerca de 10⁻⁸ de perder a corrida; um servidor a lançar 10⁴ exceções por segundo devido a contrapressão de ingestão falha então cerca de uma vez a cada poucas horas, o que, à escala da frota, é mais do que suficiente.
O Rockset era pouco comum nos três eixos que determinam a taxa de falhas. Lança exceções com frequência como controlo de sobrecarga, entrega SIGUSR2 com uma frequência inusitada através do seu coarse_thread_cputime_clock e, mais cedo nesse ano, tinha adicionado uma chamada timer_getoverrun que fazia com que o signal handler consumisse mais stack — o suficiente para alcançar e corromper o ucontext_t obsoleto. Antes dessa alteração, as falhas simplesmente não ocorriam. O bug de 18 anos esteve sempre presente; foi o produto da taxa de exceções, da taxa de sinais e do consumo de stack do handler que acabou de cruzar o limiar a partir do qual se tornou visível.
A instrumentação como verdadeira solução
A mitigação imediata foi mudar da GNU libunwind para o unwinder da libgcc — uma boa troca por si só, já que a libgcc tem recebido mais trabalho para reduzir a contenção de locks em VMs de grande dimensão — e a OpenAI submeteu à comunidade um reprodutor autocontido e a correção para a GNU libunwind. Para a falha de hardware, a equipa melhorou o fatal signal handler para registar o estado dos registos, permitindo detetar recorrências apenas a partir dos logs, sem precisar de um core dump, e alterou o control plane para reutilizar VMs em vez de as reciclar, o que facilita a deteção de nós defeituosos.
A lição fundamental não está em nenhum dos detalhes de ABI, DWARF ou mecanismos de exceção que a investigação revelou. Está no facto de a confusão só ter persistido porque dois fenómenos estavam misturados numa única história, e de se ter dissolvido no momento em que o conjunto de dados se tornou limpo e completo. É esse o ponto que vale a pena levar para outras equipas de infraestrutura: quando um bug parece impossível, o que falta é muitas vezes dados fiáveis sobre a população, e não um diagnóstico mais engenhoso de um único caso.
A fiabilidade não passa apenas por corrigir bugs depois de ocorrerem — passa por construir os dados, os fluxos de trabalho e as competências que transformam problemas impossíveis em problemas diagnosticáveis e solucionáveis.Montana Labs
Find this story relevant to you?
Contact us to find a unique solution
Precisa de um parceiro de engenharia de IA que saiba executar?
Ajudamos equipas em Portugal a integrar IA em produtos, automatizar processos de alto valor e modernizar os sistemas que suportam o negocio.
Leitura relacionada
Mais análises sobre entrega de produto, AI operacional e o trabalho de sistemas que faz com que a implementação funcione na prática.