- Le binaire x86_64-unknown-linux-musl de Ripgrep 15.2.0 se termine par intermittence avec un
SIGSEGVlors de recherches dans de très grands arbres de fichiers avec une forte concurrence - Le plantage se produit dans
calloc, appelé paropendir, et le point de vérification d’intégrité des métadonnées du tas de musl mallocng apparaît en haut de la trace de pile - L’environnement de reproduction consiste en un arbre d’environ 20 Gio et 1,8 million de fichiers, dans lequel une chaîne inexistante est recherchée à répétition avec
rg - Sur un système à 24 cœurs, si suffisamment de RAM est disponible pour que l’arbre de recherche tienne dans le cache de blocs du noyau, le problème survient généralement en environ 1 minute
- Le problème a été reproduit indépendamment non seulement avec le
rginclus dans OpenAI Codex, mais aussi avec un binaire identique octet pour octet à la version officielle, confirmant qu’il est sans rapport avec les dépendances de Codex
Environnement
- La version utilisée est ripgrep 15.2.0, révision
e89fff8, avec la fonctionnalité+pcre2- SIMD à la compilation :
+SSE2,-SSSE3,-AVX2 - SIMD à l’exécution :
+SSE2,+SSSE3,+AVX2 - PCRE2 10.45 et JIT disponibles
- SIMD à la compilation :
- Le système d’exploitation est OpenSUSE Tumbleweed Linux x86_64
- Le
rgdu bundle OpenAI Codex où le problème a été découvert initialement est identique octet pour octet à la version officielle x86_64-unknown-linux-musl - Le problème a aussi été reproduit avec le binaire officiel, indépendamment de Codex, et le binaire d’analyse a été compilé avec les symboles de débogage via la commande suivante
CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
Procédure de reproduction
- generate_repro_tree.py génère un arbre de fichiers aléatoire imitant les statistiques du dépôt dans lequel le problème s’est produit à l’origine
- Ce programme a été écrit avec un LLM
- Le résultat généré représente environ 20 Gio et 1,8 million de fichiers
- Depuis la racine de l’arbre généré, rechercher à répétition une chaîne arbitraire inexistante
while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
- Il a été observé qu’un arbre de recherche suffisamment grand est indispensable à la reproduction
- Sur un système à 24 cœurs, si assez de RAM libre est disponible pour que tout l’arbre tienne dans le cache de blocs du noyau, le plantage survient généralement après environ 1 minute
Point de plantage
- Le résultat réel est un
SIGSEGVlaissant un core dump - Le haut de la trace de pile est
get_metadans musl mallocng, et le plantage se produit au niveau de la vérification d’intégrité des métadonnées du tas - Le flux d’appels montre
opendirappelantcalloc, puis le parcours de répertoires de la bibliothèque standard Rust et les workersignore::walkde ripgrepget_meta→__malloc_allzerop→calloc→opendirstd::fs::read_dir→ignore::walk::Work::read_dir→ignore::walk::Worker::run
- Sont joints comme éléments d’analyse un core dump et le binaire rg correspondant
Comportement attendu et état actuel
- Le comportement attendu est de s’exécuter sans erreur de segmentation, même lors de recherches à grande échelle et fortement concurrentes
- Les informations fournies n’incluent pas de cause confirmée, de correctif, de résultat d’examen ni d’indication de résolution finale
1 commentaires
Avis de Hacker News
Il y a un passage intéressant dans le patch du noyau : https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...
Il y a deux ans, une telle analyse aurait été perçue comme le fruit de quelqu’un donnant généreusement de son temps à la communauté ; désormais, comme on sait que sa provenance et son coût en tokens ne représentent que 0,06 dollar, on n’a pas envie de la lire. Le fait qu’elle annonce aussi ce qui nous attend joue également
Dans quelques années, enquêter soi-même sur un bug sera un dernier recours, et d’autres agents IA liront les rapports pour vérifier les correctifs. Comme pour l’assembleur généré par les compilateurs et les gens qui utilisent leur téléphone portable en public, on passera probablement de la moquerie à l’indifférence
Je comprends qu’on garde l’allocateur par défaut de musl par commodité, mais c’est étrange de ne pas être passé à un allocateur plus rapide dans une application dont la vitesse est l’objectif même
mallocng supporte mal la contention multithread. Une application habituellement limitée par les entrées-sorties s’est retrouvée, une fois compilée avec musl, à être limitée par
mallocavec seulement 8 threads ; en passant à mimalloc, les performances ont été multipliées par 20, devenant très proches de la configuration par défaut de glibc, tout en restant un peu plus lentes que glibc+mimallocIl y a effectivement un problème intéressant ici, mais il n’aurait pas dû se manifester ainsi au départ
opendirde la libc musl. Le mécanisme de remplacement de l’allocateur utilisé par Rust ne remplace pas l’allocateur de tout le processus, mais seulement l’allocateur appelé par le code RustCela dit, si l’on doit passer par un allocateur utilisant un verrou global, il vaudrait sans doute mieux que ripgrep évite d’utiliser
opendirde la libcSi vous exécutez ripgrep sur un grand système de fichiers de cluster dans un cluster HPC, arrêtez immédiatement et repensez votre flux de travail. Ce type de tâche génère une grande quantité de petites entrées-sorties, ce qui constitue le talon d’Achille des grands systèmes de fichiers de cluster
Vous déportez vers la couche de métadonnées du système de fichiers un travail qui devrait être traité dans la couche mémoire à haut débit du cluster, et il suffit que quelques utilisateurs le fassent en même temps pour paralyser tout le système de fichiers à haut débit
Si https://isolveproblems.substack.com/p/how-microsoft-vaporize... est ne serait-ce qu’un peu juste, un seul chemin non optimisé dans l’abstraction de système de fichiers d’Azure pourrait transformer une hausse d’usage en rayon d’impact énorme
Il vaudrait peut-être mieux lier directement l’analyse du bug du noyau : https://github.com/dfoxfranke/ripgrep-3494-analysis
HeadlineOn y enchaîne des phrases du genre : « une valeur écrite par un thread dans une page anonyme fraîchement fautée disparaît lors d’une relecture par le même thread environ 10 instructions plus tard, et le backing de la page est remplacé pendant l’exécution de la fonction », « lire pagemap au moment du défaut montre que le backing est la zero page du noyau », « le mécanisme est localisé dans l’interaction entre le chemin rapide des fautes anonymes avec verrou par VMA et le TLB shootdown d’un
munmapconcurrent »Difficile de comprendre ce qu’est le
backingd’une page, ce que signifientfreshly-faultedou « environ 10 instructions plus tard », ou comment un mécanisme peut être « localisé ». Cela ressemble moins à une explication technique qu’à des mots collés les uns aux autresVoici un exemple de vraie documentation technique : https://yifan.lu/2019/01/11/the-first-f00d-exploit/
La conclusion semble être en gros que la combinaison Linux 7.0 et musl 1.2.5 pose problème, mais la reproduction ne réussit toujours qu’intermittamment sous forte charge sur le même CPU Threadripper physique, donc un problème matériel n’a pas été exclu
Cela ressemble soit à une condition de course complexe déclenchée par une migration du CPU à un moment mal choisi, soit à un bug dans le chemin de suppression des tables de pages exposant temporairement une mauvaise PTE. Je ne pense pas non plus que le PFN de la zero page soit 0
À vue de nez, il est possible que, pendant la suppression directe des tables de pages, le CPU ait été autorisé à lire une table déjà libérée et réutilisée via une entrée mise en cache d’une structure de pagination de niveau supérieur. J’ai déjà débogué ce genre de problème, et c’était vraiment atroce
Pourquoi le bug ne se produit-il qu’avec musl libc, et pas avec les autres libc ?
En temps normal, j’aurais soupçonné la taille de pile des threads de musl, mais je me demande si cela a été confirmé comme un bug du noyau