1 puntos por GN⁺ 21 일 전 | 1 comentarios | Compartir por WhatsApp
  • El contador de marcas de tiempo TCP (tcp_now) de macOS sufre un desbordamiento de 32 bits alrededor de 49.7 días después del arranque, lo que hace que el reloj TCP interno se detenga
  • Como resultado, las conexiones en estado TIME_WAIT no expiran y se acumulan, por lo que los puertos efímeros no se liberan
  • Con el paso del tiempo, el agotamiento de puertos efímeros hace que fallen todas las conexiones TCP nuevas, mientras las conexiones existentes se mantienen
  • ICMP (ping) sigue funcionando con normalidad, pero toda la funcionalidad de TCP queda inutilizada, y no se puede recuperar sin reiniciar
  • Los servidores macOS, máquinas de compilación y entornos CI que operan durante largos periodos quedan expuestos a este problema en ciclos de 49 días y 17 horas, y requieren reinicios periódicos hasta que se corrija el kernel

Contexto: conceptos básicos de TCP

  • Cuando una conexión TCP termina, no desaparece de inmediato, sino que entra en estado TIME_WAIT, una etapa necesaria para manejar paquetes retrasados y garantizar un cierre confiable
    • Esto evita que paquetes antiguos se interpreten erróneamente como parte de una conexión nueva y permite retransmitir el último ACK si se pierde
  • La duración de TIME_WAIT se define como 2 × MSL (Maximum Segment Lifetime), y en macOS está configurada en aproximadamente 30 segundos
  • MSL es el tiempo máximo durante el cual un segmento TCP puede sobrevivir en la red; en el RFC 793 se definía como 2 minutos, aunque en sistemas modernos suele ser mucho menor
  • El desbordamiento de enteros sin signo de 32 bits ocurre cuando un valor supera el máximo (4,294,967,295) y vuelve a 0; la marca de tiempo TCP de macOS (tcp_now) es un contador de 32 bits en milisegundos que aumenta desde el arranque, por lo que se desborda tras 49 días, 17 horas, 2 minutos y 47.296 segundos

Hallazgo: interrupción de conexiones TCP tras 49.7 días

  • Los servidores Mac de Photon para monitoreo de iMessage operaban 24/7, y el 30 de marzo de 2026 ocurrió un fenómeno en el que todas las nuevas conexiones TCP fallaban exactamente 49.7 días después del arranque
    • Las conexiones existentes e ICMP (ping) seguían funcionando, pero ya no era posible crear nuevos sockets TCP
  • La causa fue el desbordamiento del contador de marcas de tiempo TCP (tcp_now) en el kernel XNU, donde la lógica de validación de incremento monótono bloqueó las actualizaciones tras el wraparound y el reloj TCP interno quedó detenido
  • Como las conexiones TIME_WAIT no expiraban, los puertos efímeros dejaron de liberarse y se acumularon, haciendo imposible la recuperación sin reiniciar
  • Después de reiniciar, el mismo fenómeno volvió a repetirse en ciclos de 49.7 días

Diseño experimental: comparación del comportamiento TCP antes y después del desbordamiento

  • Hipótesis: si la recolección de basura de TIME_WAIT se detiene tras el desbordamiento, debería observarse una diferencia en el patrón de creación de conexiones TCP cortas antes y después del evento
    • Antes del desbordamiento: TIME_WAIT expira normalmente tras 30 segundos
    • Después del desbordamiento: TIME_WAIT persiste indefinidamente
  • Se ejecutó un script de prueba compuesto por tres fases
    1. Fase de monitoreo: registrar la cantidad de TIME_WAIT cada 10 segundos desde 35 minutos antes hasta 5 minutos antes del desbordamiento
    2. Fase de explosión: generar alrededor de 15 conexiones TCP cortas cada 2 segundos durante 10 minutos alrededor del desbordamiento
    3. Fase de observación: monitorear los cambios en TIME_WAIT después de detener la generación de conexiones

Resultados: estancamiento de TIME_WAIT después del desbordamiento

  • Antes del desbordamiento, la cantidad de TIME_WAIT circulaba de forma estable entre 0 y 200, confirmando un comportamiento normal de recolección
  • Inmediatamente después del desbordamiento, la cantidad de TIME_WAIT siguió aumentando y dejó de expirar por completo
  • En el caso de Machine B, 2,828 conexiones TIME_WAIT seguían sin recolectarse ni una sola después de 84 segundos, y continuaron acumulándose
  • En Machine A, la verificación manual también mostró que la cantidad de TIME_WAIT aumentaba de forma monótona y el sistema no podía recuperarse

Causa raíz: desbordamiento de 32 bits de tcp_now en el kernel XNU

  • tcp_now es un contador de 32 bits en milisegundos definido en bsd/netinet/tcp_var.h que rastrea el tiempo transcurrido desde el arranque
  • En la función calculate_tcp_clock(), la operación (uint32_t)now.tv_sec * 1000 supera el valor máximo después de 49.7 días y provoca el wraparound
  • Debido a la condición if (tmp < current_tcp_now), durante el desbordamiento el valor existente se vuelve mayor que el nuevo y se bloquea la actualización, dejando tcp_now detenido permanentemente
  • La comprobación de expiración de TIME_WAIT se realiza en función de tcp_now, por lo que, si el reloj se detiene, la condición de expiración siempre resulta falsa y ya no es posible recolectarlas

Efecto en cadena: se extiende hasta detener toda la funcionalidad TCP

  • Tras unos minutos: se detiene la recolección de TIME_WAIT y empiezan a aparecer problemas graduales en cargas con muchas conexiones cortas
  • Tras unas horas: se acumulan miles de entradas TIME_WAIT y se produce agotamiento de puertos efímeros
  • Después del agotamiento de puertos: las nuevas conexiones TCP fallan en estado SYN_SENT y solo se mantienen las ya existentes
  • Aumento brusco de carga de CPU: el kernel sigue escaneando la cola de TIME_WAIT, elevando la carga
  • El resultado final es una parálisis total de TCP, mientras ICMP sigue funcionando con normalidad
  • El único método de recuperación es reiniciar, tras lo cual vuelve a comenzar la cuenta regresiva de 49.7 días

Evidencia adicional y casos relacionados

  • RFC 7323 especifica que el bit de signo de una marca de tiempo de 32 bits en unidades de 1 ms se envuelve aproximadamente cada 24.8 días
    • En macOS, se trata del desbordamiento completo de 32 bits (49.7 días), un fallo local del kernel distinto del problema de marcas de tiempo remotas tratado en el RFC
  • En la comunidad de Apple y en proyectos de código abierto se han reportado múltiples casos con los mismos síntomas
    • Imposibilidad de establecer conexiones TCP, ping funcionando, solo se resuelve reiniciando, y aparece tras varias semanas de operación
    • El mismo patrón también se observa en Podman issue #12495, entre otros
  • Elementos en común: solo falla TCP, ICMP funciona, hay que reiniciar, ocurre en ciclos de varias semanas

Alcance del impacto

  • Puede ocurrir en sistemas macOS que lleven más de 49 días y 17 horas encendidos de forma continua
  • Los usuarios comunes se ven poco afectados porque suelen reiniciar por actualizaciones periódicas
  • Entornos de alto riesgo
    • Flotas de servidores de larga duración
    • Servidores de compilación CI/CD basados en macOS
    • Estaciones de trabajo Mac Pro
    • Macs en colocation administradas de forma remota
    • Clústeres de Mac mini para granjas de compilación e infraestructura de pruebas

Procedimiento de reproducción

  • Calcular el momento esperado del desbordamiento a partir de la hora de arranque
  • Monitorear la cantidad de TIME_WAIT antes y después del desbordamiento
  • Generar muchas conexiones TCP cortas en el momento del desbordamiento
  • Si después de 2 minutos la cantidad de TIME_WAIT no disminuye, la reproducción del bug fue exitosa

Estado del sistema observado 9.5 horas después

  • Las conexiones TIME_WAIT no se recolectaron ni una sola vez y siguieron aumentando
  • Se acumularon más de 3,000 conexiones fallidas en estado SYN_SENT
  • Solo se mantuvieron las conexiones existentes y no fue posible crear nuevas
  • La carga promedio de Machine B subió hasta 49.74, porque el kernel consumía demasiada CPU escaneando la cola de TIME_WAIT

Conclusión

  • Un único entero de 32 bits y la condición if (tmp < current_tcp_now) actúan como una bomba de tiempo que detiene por completo TCP después de 49.7 días
  • Es un tipo de fallo difícil de detectar en etapas de desarrollo, pruebas o revisión de código, y solo se manifiesta en entornos reales de operación
  • Photon reprodujo el mismo fenómeno en varios servidores y confirmó claramente que antes del desbordamiento la recolección funcionaba normalmente, y después TIME_WAIT se acumulaba
  • Cuando tcp_now se detiene, también se detiene el reloj TCP del kernel; el sistema parece estar normal a simple vista, pero todos los puertos TCP terminan agotados
  • Los administradores de sistemas macOS de larga duración deben recordar el umbral de 49 días, 17 horas, 2 minutos y 47 segundos, y ajustar los ciclos de reinicio o reiniciar periódicamente hasta que se corrija el kernel
  • Photon está desarrollando actualmente una solución temporal para restaurar tcp_now sin reiniciar

1 comentarios

 
GN⁺ 21 일 전
Comentarios de Hacker News
  • Ahora por fin entiendo por qué a veces mi iMac se quedaba sin ninguna conexión
    Jamás me había dado cuenta de que era por el uptime

  • Mientras leía el artículo, me dio demasiado la impresión de que estaba escrito por IA, así que me pregunté si realmente habían contactado a Apple
    Claro, el bug es importante, pero sentí que había mucho lenguaje exagerado
    A la mayoría de los usuarios probablemente casi no les afecte
    Si dejas la Mac en modo de reposo, el stack TCP se reinicia, así que quizá así se pueda evitar el problema
    Al final Apple lo va a corregir, pero no es motivo para entrar en pánico ahora mismo

    • Creo que yo también me topé con este problema
      Tenía una MacBook con el reposo automático desactivado que llevaba como 50 días encendida, y pasaba que respondía al ping pero no funcionaba ninguna conexión TCP
      No se arregló ni cambiando de Wi‑Fi ni conectándola por cable; en cuanto la reinicié, todo volvió a la normalidad
    • No contactaron a Apple, y parece que el autor del blog piensa intentar arreglarlo por su cuenta
      Según dijo, está “desarrollando una solución alternativa mejor que reiniciar, y mientras tanto recomienda reiniciar periódicamente”
    • Yo también dejo una Mac Mini encendida las 24 horas, y a veces cuando la red se queda colgada se arregla apagando y encendiendo el adaptador Wi‑Fi
      En esos momentos, es buena hora para reiniciar
    • Sí lo reportaron a Apple, y al parecer ya quedó registrado en su sistema interno
  • Últimamente los posts de blog escritos por IA se han vuelto muy difíciles de leer
    El estilo se siente poco natural y tarda demasiado en llegar al punto

    • Pienso igual. Leer textos escritos por IA cansa y cuesta concentrarse
    • Viendo solo el resumen hecho por IA, es simple: el problema es que la Mac no permite el rollover cuando el reloj tcp_now se desborda
  • No estoy de acuerdo con eso de que “ningún desarrollador va a probar durante 50 días”
    En la práctica basta con hacer pruebas de simulación acelerando el tiempo

    • En el kernel de Linux, para detectar este tipo de problemas, al contador jiffies se le asigna al arrancar un valor justo antes del desbordamiento
    • macOS usa el reloj de hardware, así que durante el reposo se detiene
      En estos casos, si se modifica una función como calculate_tcp_clock para pasarle el uptime como argumento, se puede verificar
    • Este tipo de enfoque también se usa mucho en pruebas de videojuegos
  • Este bug no afecta solo a OpenClaw, sino a todas las conexiones TCP

    • Ni siquiera hace falta que una conexión individual dure mucho tiempo
      Si el uptime de macOS supera los 49.7 días, todas las conexiones TCP empiezan a verse afectadas
    • También hubo la broma de que “ahora OpenClaw parece lo más importante del mundo”
  • Varios de mis equipos macOS llevan entre 600 y 1000 días encendidos, y las conexiones TCP siguen expirando normalmente
    Las versiones del kernel son 20.6.0 y 17.7.0 respectivamente
    Así que parece que este bug solo ocurre a partir de ciertas versiones

    • Según el análisis, el valor de tcp_now se queda detenido justo antes del desbordamiento, y por un wraparound incorrecto en el cálculo de temporizadores pasa a ser negativo, así que la comparación falla
      Puede haber un breve periodo en el que se acumulen conexiones TIME_WAIT, pero el texto original reaccionaba de forma exagerada y parecía escrito por un LLM
    • De hecho, dicen que este bug apareció en código nuevo introducido el año pasado en macOS 26
      Enlace relacionado en GitHub
  • Este tipo de problema se repite en distintos programas
    Hace tiempo pasó algo parecido en los servidores de Guild Wars, donde probaron sumándole cierto valor a GetTickCount() para provocar antes el desbordamiento

    • Los sistemas que manejan desbordamientos deberían probarlos forzando un desbordamiento justo después del arranque
  • Este bug recuerda al bug de los 49.7 días de Windows 95
    Artículo relacionado

  • Me pregunto qué relación hay entre OpenClaw y este bug

  • Este problema me recuerda al bug de los 208 días del scheduler del kernel de Linux
    Enlace de referencia