Ingenieros de Cloudflare desvelan el misterio de los «fallos fantasma» que durante años ha atormentado a los desarrolladores de Go.
Al analizar fallos en la infraestructura distribuida, los ingenieros de Cloudflare se toparon con un error extremadamente raro que solo se manifestaba en servidores con arquitectura arm64. La escala del sistema — decenas de millones de solicitudes HTTP por segundo, cientos de centros de datos — permitió detectarlo antes que nadie. Resultó que la causa no estaba en el hardware ni en el código de las aplicaciones, sino en el propio compilador de Go: una generación incorrecta de instrucciones provocaba una condición de carrera de datos a nivel de código máquina.
Todo empezó con pánicos impredecibles en el servicio Cloudflare, encargado de la configuración del núcleo de Linux para la enrutación de tráfico en los productos Magic Transit y Magic WAN. El sistema de monitorización registraba fallos esporádicos con mensajes sobre un «desenrollado» incompleto de la pila de llamadas. Inicialmente los ingenieros supusieron que se trataba de una corrupción aleatoria de memoria: el proceso de gestión tenía poca carga y no era crítico, por lo que pospusieron la investigación. Pero pronto el número de caídas empezó a aumentar, llegando a 30 al día. Además, no coincidían ni con actualizaciones ni con cambios en la infraestructura.
El análisis mostró que todos los fallos ocurrían en el momento del desenrollado de la pila durante la actividad del recolector de basura y afectaban a la función (*unwinder).next. A veces esto derivaba en un error de acceso a la memoria, otras veces en la terminación forzada del proceso debido a un estado inconsistente de la pila. Uno de los ingenieros observó que las referencias apuntaban a la estructura m —un elemento interno del planificador de Go responsable de la conexión entre gorutinas y los núcleos del procesador—. Dado que Go utiliza una planificación M:N, donde muchas gorutinas se asignan a un número limitado de hilos, esto indicaba una posible discrepancia entre el contexto activo y la pila de llamadas.
El problema no estaba en la aplicación, sino en el código generado. Casi todos los trazados señalaban a la biblioteca Netlink, usada para interactuar con el núcleo de Linux. Los fallos surgían cuando se interrumpía de forma asíncrona una gorutina que ejecutaba el método Receive. Con el paso de Go a desplazamiento por expulsión asíncrona (a partir de la versión 1.14) el planificador puede detener forzosamente funciones largas enviando la señal SIGURG. Sin embargo, en este caso la interrupción ocurría en medio del epílogo de la función —entre dos instrucciones ADD que ajustaban sucesivamente el puntero de pila—. Si la detención se producía exactamente entre ellas, la pila quedaba en un estado incorrecto y, al desenrollarse después, el recolector de basura accedía a una dirección errónea, provocando un fallo de segmentación.
Para confirmar la hipótesis, los especialistas de Cloudflare crearon un ejemplo mínimo en Go puro, donde la función obligaba artificialmente al compilador a dividir el ajuste del puntero de pila en dos operaciones. Con el recolector de basura activado y llamadas continuas a esa función, la aplicación caía de forma estable al cabo de un minuto y medio. Esto demostró que no se trataba de un error de la biblioteca, sino de un defecto del compilador de Go que permitía la posibilidad de una interrupción en la «ventana» entre dos instrucciones máquina.
El error resultó ser literalmente una condición de carrera de una sola instrucción: la interrupción podía suceder en un breve intervalo entre dos ADD, lo que hacía el fallo extremadamente raro pero reproducible. El problema afectaba a las versiones de Go anteriores a las correcciones introducidas en 1.23.12, 1.24.6 y 1.25.0, donde el comportamiento fue arreglado. Tras la actualización, el compilador dejó de dividir el ajuste del puntero de pila en varias instrucciones: ahora el desplazamiento se calcula en un registro temporal y se aplica con una sola operación indivisible. Esto eliminó la posibilidad de interrupción en medio del cambio de pila y solucionó por completo el fallo.
Los ingenieros de Cloudflare señalaron que casos así son una rara oportunidad para literalmente «capturar» un error en el compilador en producción. Sin la escala gigantesca de su infraestructura, la compañía probablemente nunca se habría topado con esta situación. Este incidente es un ejemplo de cómo incluso en lenguajes y arquitecturas maduras puede esconderse una vulnerabilidad que solo se manifiesta al combinar alta carga y una concatenación precisa de circunstancias, y se presentó como una vulnerabilidad que solo aparece en esas condiciones.