Bug descoberto no macOS interrompe a rede TCP após 49,7 dias
(photon.codes)- O contador de timestamp TCP (
tcp_now) do macOS sofre overflow de 32 bits cerca de 49,7 dias após o boot, fazendo com que o relógio interno do TCP pare - Com isso, conexões em estado TIME_WAIT não expiram e ficam se acumulando, impedindo a liberação de portas efêmeras
- Com o tempo, o esgotamento de portas efêmeras faz com que todas as novas conexões TCP falhem, mantendo apenas as conexões já existentes
- O ICMP (
ping) continua funcionando normalmente, mas toda a funcionalidade do TCP fica paralisada, sem possibilidade de recuperação além de reiniciar - Servidores macOS, máquinas de build e ambientes de CI com longa duração de execução ficam expostos a esse problema em um ciclo de 49 dias e 17 horas, exigindo reinicializações periódicas até que o kernel seja corrigido
Contexto: conceitos básicos de TCP
- Quando uma conexão TCP é encerrada, ela não desaparece imediatamente; entra no estado TIME_WAIT, uma etapa necessária para lidar com pacotes atrasados e garantir um encerramento confiável
- Isso evita que pacotes antigos sejam interpretados incorretamente como parte de uma nova conexão e permite retransmitir o último ACK se ele se perder
- A duração do TIME_WAIT é definida como 2 × MSL (Maximum Segment Lifetime) e, no macOS, está configurada em cerca de 30 segundos
- MSL é o tempo máximo que um segmento TCP pode permanecer vivo na rede; no RFC 793 ele é definido como 2 minutos, mas em sistemas modernos costuma ser bem menor
- Overflow de inteiro sem sinal de 32 bits é o fenômeno em que o valor volta para 0 ao ultrapassar o máximo (4.294.967.295). No macOS, o timestamp TCP (
tcp_now) é um contador de 32 bits em milissegundos que aumenta desde o boot, e o overflow ocorre após 49 dias, 17 horas, 2 minutos e 47,296 segundos
Descoberta: interrupção das conexões TCP após 49,7 dias
- Os servidores Mac da Photon para monitoramento do iMessage operavam 24/7 e, em 30 de março de 2026, exatamente 49,7 dias após o boot, ocorreu um fenômeno em que todas as novas conexões TCP passaram a falhar
- As conexões existentes e o ICMP (
ping) continuavam funcionando, mas não era mais possível criar novos sockets TCP
- As conexões existentes e o ICMP (
- A causa foi o overflow do contador de timestamp TCP (
tcp_now) no kernel XNU, em que a lógica de validação de crescimento monotônico bloqueia atualizações após o wraparound, fazendo o relógio interno do TCP parar - Como as conexões TIME_WAIT não expiravam, as portas efêmeras deixavam de ser liberadas e se acumulavam, tornando a recuperação impossível sem reiniciar
- Após o reboot, o mesmo fenômeno voltava a se repetir em ciclos de 49,7 dias
Desenho do experimento: comparação do comportamento do TCP antes e depois do overflow
- Hipótese: se a coleta de lixo de TIME_WAIT parar após o overflow, o padrão de criação de conexões TCP curtas deve mudar antes e depois do evento
- Antes do overflow: TIME_WAIT expira normalmente após 30 segundos
- Depois do overflow: TIME_WAIT permanece indefinidamente
- Foi executado um script de teste em três etapas
- Etapa de monitoramento: registro da contagem de TIME_WAIT a cada 10 segundos, de 35 minutos antes até 5 minutos antes do overflow
- Etapa de explosão: criação de cerca de 15 conexões TCP curtas a cada 2 segundos durante 10 minutos ao redor do instante do overflow
- Etapa de observação: monitoramento da variação do TIME_WAIT após interromper a criação de conexões
Resultado: TIME_WAIT estagnado após o overflow
- Antes do overflow, a quantidade de TIME_WAIT circulava de forma estável entre 0 e 200, confirmando um comportamento normal de reciclagem
- Logo após o overflow, o número de TIME_WAIT passou a subir continuamente e deixou de expirar
- No caso da Machine B, 2.828 conexões TIME_WAIT não tiveram nenhuma reciclagem nem após 84 segundos, e continuaram se acumulando depois disso
- Na Machine A, a verificação manual também mostrou crescimento monotônico do TIME_WAIT, em um estado sem recuperação possível
Causa raiz: overflow de 32 bits de tcp_now no kernel XNU
tcp_nowé um contador de 32 bits em milissegundos definido embsd/netinet/tcp_var.h, usado para rastrear o tempo decorrido desde o boot- Na função
calculate_tcp_clock(), a operação(uint32_t)now.tv_sec * 1000ultrapassa o valor máximo após 49,7 dias, provocando wraparound - Por causa da condição
if (tmp < current_tcp_now), no momento do overflow o valor existente fica maior que o novo valor, então a atualização é bloqueada e otcp_nowfica permanentemente parado - Como a verificação de expiração do TIME_WAIT é feita com base em
tcp_now, quando o relógio para a condição de expiração se torna sempre falsa, impedindo qualquer reciclagem
Efeito em cascata: paralisação de toda a funcionalidade do TCP
- Após alguns minutos: a reciclagem de TIME_WAIT para, e cargas com muitas conexões curtas começam a apresentar problemas gradualmente
- Após algumas horas: milhares de entradas TIME_WAIT se acumulam, levando ao esgotamento de portas efêmeras
- Depois do esgotamento das portas: novas conexões TCP falham no estado SYN_SENT, enquanto as conexões existentes permanecem
- Aumento acentuado de carga de CPU: o kernel continua varrendo a fila TIME_WAIT, elevando a carga
- Resultado final: paralisação completa do TCP, com apenas o ICMP funcionando normalmente
- A única forma de recuperação é reiniciar, o que reinicia novamente a contagem dos 49,7 dias
Evidências adicionais e casos relacionados
- O RFC 7323 afirma que o wrap do bit de sinal em timestamps de 32 bits com resolução de 1 ms ocorre a cada cerca de 24,8 dias
- No caso do macOS, trata-se do overflow completo de 32 bits (49,7 dias), um defeito local do kernel separado do problema de timestamps remotos tratado pela RFC
- O mesmo sintoma foi relatado diversas vezes em comunidades da Apple e projetos open source
- Impossibilidade de estabelecer conexões TCP,
pingfuncionando normalmente, solução apenas com reboot e ocorrência após semanas de uptime - O mesmo padrão também aparece em casos como o issue #12495 do Podman
- Impossibilidade de estabelecer conexões TCP,
- Padrões em comum: falha apenas no TCP, ICMP normal, necessidade de reboot e ciclo de ocorrência de várias semanas
Escopo do impacto
- Pode ocorrer em sistemas macOS com mais de 49 dias e 17 horas de funcionamento contínuo
- Usuários comuns tendem a ser pouco afetados porque atualizações periódicas normalmente exigem reinicialização
- Ambientes de alto risco
- Frotas de servidores com longa execução
- Servidores de build CI/CD baseados em macOS
- Workstations Mac Pro
- Macs em colocation gerenciados remotamente
- Clusters de Mac mini usados em fazendas de build e infraestrutura de testes
Procedimento de reprodução
- Calcular o instante previsto do overflow a partir do horário de boot
- Monitorar a contagem de TIME_WAIT antes e depois do overflow
- Gerar várias conexões TCP curtas no momento do overflow
- Se, após 2 minutos, a contagem de TIME_WAIT não diminuir, a reprodução do bug foi bem-sucedida
Estado do sistema observado após 9,5 horas
- Nem uma única conexão TIME_WAIT foi reciclada, e a contagem continuou subindo
- Mais de 3.000 conexões com falha no estado SYN_SENT se acumularam
- Apenas as conexões existentes permaneceram; novas conexões eram impossíveis
- A carga média da Machine B chegou a 49,74, com o kernel consumindo CPU em excesso ao varrer a fila TIME_WAIT
Conclusão
- Um único inteiro de 32 bits e a condição
if (tmp < current_tcp_now)funcionam como uma bomba-relógio que para todo o TCP após 49,7 dias - É um tipo de falha difícil de detectar em fases de desenvolvimento, teste e code review, aparecendo apenas em ambientes reais de operação
- A Photon reproduziu o mesmo fenômeno em vários servidores e confirmou claramente que, antes do overflow há reciclagem normal, depois dele há acúmulo de TIME_WAIT
- Quando
tcp_nowpara, o relógio TCP do kernel congela e o sistema parece normal à primeira vista, mas todas as portas TCP acabam se esgotando - Administradores de sistemas macOS com longa execução devem se lembrar de 49 dias, 17 horas, 2 minutos e 47 segundos e precisam de ajustar o ciclo de reinicialização ou reiniciar periodicamente até que o kernel seja corrigido
- A Photon está atualmente desenvolvendo uma solução alternativa para restaurar
tcp_nowsem reiniciar
1 comentários
Comentários do Hacker News
Agora finalmente entendi por que meu iMac às vezes ficava sem conseguir fazer nenhuma conexão
Eu não fazia ideia de que era por causa do uptime
Enquanto lia o texto, a sensação de que ele foi escrito por IA era forte demais, então fiquei curioso se realmente entraram em contato com a Apple
Claro que o bug é importante, mas achei que havia muita linguagem exagerada
A maioria dos usuários provavelmente quase não será afetada
Se você deixar o Mac em modo de suspensão, a stack TCP é resetada, então talvez dê para evitar o problema assim
No fim, a Apple vai corrigir isso, mas não é motivo para pânico agora
Um MacBook com a suspensão automática desativada ficou ligado por cerca de 50 dias, e eu tinha a situação de ping funcionar, mas conexões TCP não funcionarem de jeito nenhum
Trocar o Wi‑Fi ou usar conexão cabeada não resolveu, e assim que reiniciei tudo voltou ao normal
Disseram que estão “desenvolvendo uma solução alternativa melhor do que reiniciar e, até lá, é para reiniciar periodicamente”
Nesses momentos, é uma boa hora para reiniciar
Hoje em dia está muito difícil ler posts de blog escritos por IA
O estilo é artificial e demora demais para chegar ao ponto
tcp_nowsofre overflowNão concordo com a frase “não existe desenvolvedor que vá testar por 50 dias”
Na prática, dá para fazer testes de simulação acelerando o tempo
Nesses casos, dá para validar alterando uma função como
calculate_tcp_clockpara passar o uptime como argumentoEsse bug afeta não só o OpenClaw, mas todas as conexões TCP
Quando o uptime do macOS passa de 49,7 dias, todas as conexões TCP começam a ser afetadas
Vários dos meus dispositivos macOS ficam ligados há mais de 600 a 1000 dias, e as conexões TCP estão expirando normalmente
As versões do kernel são 20.6.0 e 17.7.0, respectivamente
Então parece que esse bug só acontece a partir de certas versões
tcp_nowpara logo antes do overflow, e por causa de um wraparound incorreto no cálculo do timer ele vira negativo, fazendo a comparação falharPode haver um curto período em que conexões TIME_WAIT se acumulam, mas o texto original exagerou na reação e parecia ter sido escrito por um LLM
Link relacionado no GitHub
Esse tipo de problema se repete em vários softwares
Antigamente aconteceu algo parecido nos servidores de Guild Wars, e eles testavam somando um valor específico a
GetTickCount()para induzir o overflow mais rápidoEsse bug lembra o bug dos 49,7 dias do Windows 95
Texto relacionado
Fiquei curioso sobre qual é a relação entre o OpenClaw e esse bug
Esse problema lembra o bug dos 208 dias no escalonador do kernel Linux
Link de referência