Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
Programação

macOS Tem o Mesmo Bug do Windows 95 — e a Apple Não Percebeu

Email : 80

O Fantasma do Windows 95 Voltou — e Mora no Seu Mac

Em 1995, o Windows 95 tinha um bug infame: depois de exatamente 49,7 dias ligado, o sistema travava. A causa? Um contador de 32 bits que media o tempo em milissegundos e simplesmente estourava. Todo mundo riu. A Microsoft corrigiu. A vida seguiu.

Trinta e um anos depois, a Apple cometeu o mesmo erro. Exatamente o mesmo.

Uma empresa chamada Photon descobriu que o macOS 26 (Sequoia) carrega uma bomba-relógio no stack TCP: depois de 49 dias, 17 horas, 2 minutos e 47 segundos de uptime, a rede TCP para de funcionar. Novas conexões ficam presas em SYN_SENT, portas efêmeras se esgotam, e aplicações começam a dar timeout sem explicação aparente.

Se você roda um Mac como servidor, homelab, ou simplesmente nunca desliga a máquina — isso te afeta diretamente.

O Que Acontece Tecnicamente

O problema está na função calculate_tcp_clock() do kernel do macOS. Essa função usa microuptime() para obter o tempo de atividade do sistema em microssegundos, converte para milissegundos e armazena o resultado em uma variável uint32_t chamada tcp_now.

Um uint32_t armazena valores de 0 a 4.294.967.295. Quando o uptime em milissegundos ultrapassa esse limite — o que acontece em exatamente 49,7 dias — o cast para uint32_t faz o valor “dar a volta” e recomeçar do zero.

Até aqui, overflow de inteiro. Coisa que qualquer programador aprende no primeiro semestre da faculdade. (Aliás, recentemente um bug no kernel Linux passou 23 anos sem ser detectado — bugs de kernel adoram se esconder.) Mas o problema real é o que acontece depois.

A Cascata de Falhas

Quando tcp_now congela (ou reseta para um valor próximo de zero), o sistema de timers TCP quebra:


// Pseudocódigo simplificado do que acontece no kernel
if (TSTMP_GEQ(tcp_now, timer->expiry)) {
    // Limpa conexão TIME_WAIT
    cleanup_connection();
}

A macro TSTMP_GEQ compara timestamps TCP considerando wrapping de 32 bits. Quando tcp_now congela no valor pré-overflow, essa comparação retorna false para qualquer timer novo — porque o sistema acha que o tempo “parou”.

O resultado prático:

Fase O que acontece Impacto
—— ————— ———
0-49 dias Tudo normal Nenhum
49,7 dias tcp_now congela Timers param de expirar
49,7+ dias TIME_WAIT acumula Portas efêmeras esgotam
50+ dias Novas conexões falham Apps perdem conectividade

Conexões em TIME_WAIT nunca expiram. Elas se acumulam indefinidamente. E como o pool de portas efêmeras do macOS é finito (por padrão, entre 49152 e 65535 — apenas 16.383 portas), bastam algumas horas após o trigger para que novas conexões TCP sejam impossíveis.

Por Que 49,7 Dias? A Matemática do Overflow

O número não é arbitrário. É pura aritmética de 32 bits:


2³² milissegundos = 4.294.967.296 ms
4.294.967.296 ms ÷ 1000 = 4.294.967 segundos
4.294.967 s ÷ 60 = 71.582 minutos
71.582 min ÷ 60 = 1.193 horas
1.193 h ÷ 24 = 49,71 dias

Ou seja: 49 dias, 17 horas, 2 minutos e 47,296 segundos. Esse é o limite exato de um contador de milissegundos em 32 bits unsigned.

É o mesmo cálculo que causou o crash do Windows 95. O mesmo que já derrubou sensores IoT, controladores industriais e até o Boeing 787 (que precisava ser reiniciado a cada 248 dias por causa de um overflow similar em um contador de centésimos de segundo).

A Ironia Histórica: De Redmond a Cupertino

Vamos recapitular rapidamente a timeline de bugs de overflow de timer:

1995 — Windows 95: O VTDAPI.VXD usava um uint32_t para contar milissegundos. Overflow em 49,7 dias causava travamento completo. A Microsoft lançou um patch e virou piada na comunidade tech por anos.

2003 — Windows Server 2003/Vista/7: Informações de conexão TCP armazenadas em centésimos de segundo num uint32_t. Conexões falhavam depois de 497 dias. Menos dramático, mas igualmente constrangedor.

2015 — Boeing 787 Dreamliner: A FAA emitiu uma diretiva obrigando reinicialização dos geradores elétricos a cada 248 dias, porque um overflow no contador interno podia causar perda de energia em voo. Nada como overflow de inteiro a 11.000 metros de altitude.

2026 — macOS Sequoia: calculate_tcp_clock() congela depois de 49,7 dias. A Apple, que se orgulha de qualidade de software, reproduz um bug que a Microsoft corrigiu há 31 anos. (E não é a única dor de cabeça: recentemente a Apple também teve que lidar com drivers Nvidia no Mac depois de anos de bloqueio.)

A comunidade do Hacker News não perdoou. Um comentário resumiu bem: “Eu achava que overflow de 32 bits era uma questão resolvida desde o Y2K. Aparentemente a Apple discorda.”

Quem É Afetado (e Quem Não É)

Antes de entrar em pânico: a maioria dos usuários de Mac nunca vai encontrar esse bug. Eis o motivo:

O sleep reseta o problema. Quando você fecha a tampa do MacBook ou coloca o sistema em standby, o stack TCP é reinicializado. O contador de uptime relevante para o bug para de avançar. Abriu de novo? Recomeça do zero.

Isso significa que o típico MacBook que você usa no dia a dia, fecha à noite e leva na mochila, nunca vai acumular 49 dias de uptime contínuo.

Quem realmente precisa se preocupar:

  • Mac Minis e Mac Studios usados como servidores — máquinas que ficam ligadas 24/7 sem suspender
  • Mac Pros em render farms — uptime contínuo é comum
  • Desenvolvedores com homelabs — se você roda Docker, Kubernetes ou VMs no Mac sem reiniciar
  • Qualquer Mac configurado com “Prevent sleep” em Energy Saver

Um detalhe importante: conexões persistentes sofrem mais que conexões curtas. Se você mantém túneis SSH, conexões de banco de dados ou WebSockets abertos, o impacto é mais severo e mais rápido.

Como Verificar Se Você Está em Risco

Quer saber o uptime do seu Mac? Abra o Terminal:


uptime
# Exemplo de saída:
# 10:23  up 52 days, 3:41, 2 users, load averages: 1.23 0.98 0.87

Se o número passar de 45 dias, é hora de reiniciar. Simples assim.

Para monitorar conexões TIME_WAIT acumulando:


netstat -an | grep TIME_WAIT | wc -l

Se esse número estiver crescendo consistentemente sem cair, você provavelmente está sendo afetado.

E para verificar portas efêmeras disponíveis:


sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
# Padrão: 49152 a 65535 (16.383 portas)

O Workaround: Simples e Eficaz

Não existe patch da Apple no momento da publicação deste artigo. Mas o workaround é trivial:

Reinicie o Mac periodicamente. Uma vez por mês é suficiente.

Se reiniciar não é opção (servidores em produção, por exemplo), você pode agendar um script de monitoramento:


#!/bin/bash
# Verifica uptime e alerta se > 40 dias
UPTIME_DAYS=$(awk '{print int($1/86400)}' /proc/uptime 2>/dev/null || \
  sysctl -n kern.boottime | awk '{print int((systime()-$4)/86400)}')

if [ "$UPTIME_DAYS" -gt 40 ]; then
    echo "ALERTA: Mac com $UPTIME_DAYS dias de uptime. Reinicie antes de 49 dias."
    # Adicione notificação via Slack, email, etc.
fi

Outra opção é simplesmente colocar o Mac em sleep por alguns segundos via automação:


# Força sleep por 5 segundos e acorda
sudo pmset sleepnow
# (configure um wake timer no Energy Saver)

Mas, honestamente? Se você depende de um Mac para workloads de servidor com uptime crítico, talvez seja hora de repensar suas escolhas de infraestrutura.

O Elefante na Sala: Qualidade de Software da Apple

Eu vou ser direto aqui: esse bug é embaraçoso.

Não é um edge case obscuro num driver de impressora. É o stack TCP do sistema operacional. A parte que conecta literalmente tudo à internet. E o bug é um overflow de uint32_t — a categoria de vulnerabilidade mais básica que existe.

O macOS é construído sobre o Darwin, que por sua vez descende do FreeBSD e do Mach kernel. O FreeBSD não tem esse bug. O Linux não tem esse bug. O próprio Windows já corrigiu esse bug há décadas.

A Apple introduziu essa regressão no macOS 26. Ou seja, não é código legado que ninguém mexeu — alguém escreveu (ou reescreveu) calculate_tcp_clock() recentemente e não testou com uptimes longos.

Isso levanta questões sérias:

  • Os testes de regressão da Apple cobrem uptimes longos? Aparentemente não.
  • Existe fuzzing nos timers do kernel? Se existe, não pegou um overflow de 32 bits.
  • Code review interno verificou os casts? Um uint32_t para timestamps em milissegundos deveria ter levantado uma flag imediatamente.

A comunidade de desenvolvimento tem discutido bastante sobre a crescente lista de bugs de qualidade no macOS nos últimos anos. Kernel panics mais frequentes, problemas de Bluetooth que nunca são resolvidos, o fiasco do WindowServer consumindo GPU… Esse bug TCP é mais um item na lista.

Como a Apple Deveria Corrigir

A correção é conceitualmente simples. Existem pelo menos duas abordagens:

Opção 1: Usar uint64_t para o timestamp.


// Antes (vulnerável)
uint32_t tcp_now;
tcp_now = (uint32_t)(microuptime_ms);

// Depois (seguro por 584 milhões de anos)
uint64_t tcp_now;
tcp_now = microuptime_ms;

Um uint64_t em milissegundos estoura depois de 584.942.417 anos. Acho que dá tempo.

Opção 2: Tratar o wraparound corretamente.


// Usar aritmética modular consciente do overflow
static inline bool tcp_timer_expired(uint32_t now, uint32_t expiry) {
    return (int32_t)(now - expiry) >= 0;
}

Essa abordagem já é usada no Linux há décadas. O cast para int32_t faz a subtração funcionar corretamente mesmo quando now dá a volta.

A opção 1 é mais segura e mais limpa. A opção 2 é um patch menor e mais rápido. Qualquer uma resolve.

Lições Para Todo Desenvolvedor

Esse bug não é relevante só pra quem usa Mac. Ele é um lembrete para qualquer pessoa que escreve código:

1. Timestamps em 32 bits são uma bomba-relógio. Sempre. Sem exceção. Se você está contando milissegundos em uint32_t, você tem 49 dias até o overflow. Se está contando segundos, tem 136 anos — mas um int32_t (signed) te dá só 68 anos, o que é exatamente o problema do Y2038.

2. Testes de longa duração existem por um motivo. Ninguém vai rodar um teste de 50 dias. Mas você pode simular: mock o timer, avance artificialmente para perto do limite, e veja o que acontece.


# Teste de overflow em Python (conceito)
import ctypes

def simulate_tcp_clock(uptime_ms):
    tcp_now = ctypes.c_uint32(uptime_ms).value
    return tcp_now

# Teste no limite
assert simulate_tcp_clock(2**32 - 1) == 4294967295  # Último valor válido
assert simulate_tcp_clock(2**32) == 0                 # Overflow!
assert simulate_tcp_clock(2**32 + 1000) == 1000       # Valores "do futuro"

3. Casts implícitos são silenciosos e perigosos. O compilador C não avisa quando você faz uint32_t x = (uint32_t)valor_64bits. O valor simplesmente é truncado. Ferramentas como -Wconversion no GCC/Clang podem ajudar, mas precisam ser habilitadas manualmente.

4. Se já aconteceu antes, vai acontecer de novo. O Windows 95 não foi a primeira vítima de overflow de timer, e o macOS 26 não será a última. Documente, teste e trate seus limites numéricos como cidadãos de primeira classe.

Outros Sistemas Operacionais: Como Eles Resolvem

Vale comparar como diferentes sistemas lidam com o mesmo problema:

Sistema Timer TCP Tipo Overflow em Status
——— ———– —— ————- ——–
Linux tcp_time_stamp u32 com wrap handling Nunca (aritmética modular) Seguro
FreeBSD tcp_ts_getticks() uint32_t com wrap Nunca (tratado) Seguro
Windows 10+ Performance Counter uint64_t ~584M anos Seguro
macOS 26 tcp_now uint32_t sem wrap 49,7 dias Vulnerável

O Linux resolveu isso há mais de 20 anos usando aritmética modular em todas as comparações de timestamp TCP. O kernel tem macros específicas como before() e after() que tratam wraparound de 32 bits corretamente. Não é ciência de foguete — é engenharia de software básica.

O FreeBSD, do qual o Darwin (base do macOS) descende, também trata isso corretamente. O que sugere que a Apple introduziu o bug ao reescrever ou refatorar o código de timers TCP no macOS 26, possivelmente durante a migração para uma nova arquitetura de rede ou otimização de performance.

O Que Fazer Agora

Se você é um usuário de Mac casual que fecha o laptop todo dia: relaxe. Você nunca vai encontrar esse bug.

Se você roda Macs como servidores ou workstations 24/7:

  1. Verifique o uptime agora (uptime no Terminal)
  2. Se estiver acima de 40 dias, reinicie
  3. Configure um lembrete mensal para reiniciar
  4. Monitore conexões TIME_WAIT periodicamente
  5. Fique de olho nas atualizações do macOS — a Apple provavelmente vai corrigir isso em breve (ou pelo menos deveria)

E se você é desenvolvedor: use esse bug como motivação para auditar seus próprios timers. Quantos uint32_t você tem no seu código contando milissegundos? Quantos deles vão estourar silenciosamente algum dia?

A Apple que o diga — 31 anos depois do Windows 95, o mesmo bug continua mordendo quem não presta atenção.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts