- El binario x86_64-unknown-linux-musl de Ripgrep 15.2.0 termina de forma intermitente con
SIGSEGVal buscar en árboles de archivos grandes con alta concurrencia - El fallo ocurre dentro de
calloc, llamado poropendir, y el punto de verificación de integridad de metadatos del heap de musl mallocng aparece en la parte superior del seguimiento de pila - El entorno de reproducción consiste en un árbol de aproximadamente 20 GiB y 1.8 millones de archivos, donde se busca repetidamente con
rguna cadena inexistente - En un sistema de 24 núcleos, si hay suficiente RAM para que el árbol de búsqueda quepa en la caché de bloques del kernel, el problema suele aparecer en aproximadamente 1 minuto
- Se reprodujo de forma independiente no solo con el
rgincluido en OpenAI Codex, sino también con un binario idéntico byte a byte al lanzamiento oficial, por lo que se confirmó que es un problema no relacionado con las dependencias de Codex
Entorno afectado
- La versión usada es ripgrep 15.2.0 rev
e89fff8, e incluye la función+pcre2- SIMD en compilación:
+SSE2,-SSSE3,-AVX2 - SIMD en ejecución:
+SSE2,+SSSE3,+AVX2 - PCRE2 10.45 y JIT están disponibles
- SIMD en compilación:
- El sistema operativo es OpenSUSE Tumbleweed Linux x86_64
- El
rgincluido en OpenAI Codex donde se descubrió inicialmente es idéntico byte a byte al lanzamiento oficial x86_64-unknown-linux-musl - También se reprodujo con el binario oficial, independientemente de Codex; el binario de análisis se compiló con símbolos de depuración mediante el siguiente comando
CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
Pasos para reproducirlo
- generate_repro_tree.py genera un árbol de archivos aleatorio que imita las estadísticas del repositorio donde apareció originalmente el problema
- Este programa fue escrito con un LLM
- El resultado generado tiene un tamaño aproximado de 20 GiB y 1.8 millones de archivos
- Desde la raíz del árbol generado, buscar repetidamente una cadena arbitraria inexistente
while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
- Se observó que un árbol de búsqueda suficientemente grande es esencial para reproducirlo
- En un sistema de 24 núcleos, si hay suficiente RAM libre para que todo el árbol entre en la caché de bloques del kernel, normalmente falla después de aproximadamente 1 minuto
Punto del fallo
- El resultado real es un
SIGSEGVque deja un volcado de core - La parte superior del seguimiento de pila es
get_metade musl mallocng, y el fallo ocurre en el punto de verificación de integridad de los metadatos del heap - El flujo de llamadas es que
opendirllama acalloc, y luego continúa con el recorrido de directorios de la biblioteca estándar de Rust y los workers deignore::walkde ripgrepget_meta→__malloc_allzerop→calloc→opendirstd::fs::read_dir→ignore::walk::Work::read_dir→ignore::walk::Worker::run
- Como material de análisis se adjuntaron el volcado de core y el binario rg correspondiente
Comportamiento esperado y estado actual
- El comportamiento esperado es que se ejecute sin errores de segmentación incluso en búsquedas grandes y con alta concurrencia
- El contenido proporcionado no incluye una causa definitiva, una corrección, resultados de revisión ni si el problema quedó resuelto
1 comentarios
Comentarios en Hacker News
Hay una parte interesante en el parche del kernel: https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...
Hace 2 años, un análisis así se habría visto como el resultado de donar generosamente tiempo a la comunidad, pero ahora, sabiendo que la fuente y el costo de tokens es apenas de 0.06 dólares, ya ni lo quiero leer. También influye que anticipa lo que viene
En unos años, que una persona investigue un bug directamente será el último recurso, y otros agentes de IA leerán el reporte para validar las correcciones. Igual que ignoramos el ensamblador generado por el compilador y a la gente usando celulares en público, parece que pasaremos de la etapa de burla a la de indiferencia
Entiendo usar el asignador predeterminado de musl por comodidad, pero en una aplicación cuyo objetivo es la velocidad misma es raro no cambiarlo por uno más rápido
mallocng es vulnerable a la contención multihilo. Una aplicación que normalmente estaba limitada por I/O, al compilarla con musl, ya sufría un cuello de botella en
malloccon solo 8 hilos, y al cambiar a mimalloc el rendimiento mejoró 20 veces, quedando muy cerca de la configuración predeterminada de glibc, aunque un poco por debajo de glibc+mimallocEn efecto hay un problema interesante, pero para empezar no debería haberse manifestado de esta manera
opendirde musl libc. La forma en que Rust reemplaza el asignador no cambia el asignador de todo el proceso, sino solo el que llama el código RustAun así, si hay que pasar por un asignador con bloqueo global, parece mejor que ripgrep evite usar
opendirde libcSi estás ejecutando ripgrep contra un sistema de archivos de clúster a gran escala en un clúster HPC, deberías detenerte de inmediato y rediseñar tu flujo de trabajo. Este tipo de tarea genera grandes cantidades de I/O pequeño, y ese es el talón de Aquiles de los sistemas de archivos de clúster a gran escala
Terminas trasladando al plano de metadatos del sistema de archivos un trabajo que debería resolverse en la jerarquía de memoria de alto ancho de banda del clúster, y basta con que unos pocos usuarios lo ejecuten al mismo tiempo para dejar paralizado todo el sistema de archivos de alto ancho de banda
Si aunque sea un poco de lo que dice https://isolveproblems.substack.com/p/how-microsoft-vaporize... es cierto, entonces basta con una sola ruta no optimizada en la abstracción de sistema de archivos de Azure para que un aumento de uso se convierta en un radio de impacto enorme
Tal vez sea mejor enlazar directamente el análisis del bug del kernel: https://github.com/dfoxfranke/ripgrep-3494-analysis
HeadlineSiguen frases como: “el valor almacenado por un hilo en una página anónima recién page-faulted desaparece en una relectura del mismo hilo unas 10 instrucciones después, y el backing de la página se reemplaza durante la ejecución de la función”, “si lees pagemap en el momento del fault, el backing es la zero page del kernel”, y “el mecanismo se localiza en la interacción entre la ruta rápida de anonymous fault con bloqueo por VMA y el TLB shootdown de un
munmapconcurrente”Es difícil entender qué significa el
backingde una página, qué quiere decirfreshly-faultedo “unas 10 instrucciones después”, o cómo se supone que un mecanismo se “localiza”. Más que una explicación técnica, parece texto pegando palabras unas con otrasUn ejemplo de documentación técnica bien hecha sería este: https://yifan.lu/2019/01/11/the-first-f00d-exploit/
La conclusión parece ser, más o menos, que hay un problema con la combinación de Linux 7.0 y musl 1.2.5, pero la reproducción sigue logrando éxito solo de forma intermitente bajo carga fuerte en el mismo CPU físico Threadripper, así que no pudieron descartar un problema de hardware
Parece una condición de carrera compleja causada por una migración de CPU en un momento incómodo, o un bug donde la ruta de eliminación de tablas de páginas expuso temporalmente la PTE equivocada. Tampoco creo que el PFN de la zero page sea 0
Si tuviera que adivinar, es posible que durante la eliminación directa de la tabla de páginas se haya permitido que la CPU leyera una tabla ya liberada y reutilizada mediante una entrada en caché de una estructura de paginación superior. Ya tuve que depurar algo así antes y fue realmente horrible
¿Por qué el bug ocurre solo en musl libc y no en otras libc?
Normalmente habría sospechado del tamaño de la pila de hilos en musl, pero me pregunto si ya se confirmó que era un bug del kernel