1 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Le binaire x86_64-unknown-linux-musl de Ripgrep 15.2.0 se termine par intermittence avec un SIGSEGV lors de recherches dans de très grands arbres de fichiers avec une forte concurrence
  • Le plantage se produit dans calloc, appelé par opendir, 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 rg inclus 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
  • Le système d’exploitation est OpenSUSE Tumbleweed Linux x86_64
  • Le rg du 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 SIGSEGV laissant un core dump
  • Le haut de la trace de pile est get_meta dans 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 opendir appelant calloc, puis le parcours de répertoires de la bibliothèque standard Rust et les workers ignore::walk de ripgrep
    • get_meta__malloc_allzeropcallocopendir
    • std::fs::read_dirignore::walk::Work::read_dirignore::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

 
GN⁺ 3 시간 전
Avis de Hacker News
  • Il y a un passage intéressant dans le patch du noyau : https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...

    J’ai vu un rapport de bug intéressant dans ripgrep, ainsi qu’une analyse générée par IA, consciencieuse mais franchement assez médiocre
    Cela fait référence à https://github.com/dfoxfranke/ripgrep-3494-analysis, et moi aussi je me suis dit que c’était trop long pour avoir été écrit par un humain. En plus, ce fil semble avoir été publié aujourd’hui même

    • C’était pénible à lire, et nulle part dans ce texte interminable je n’ai trouvé un passage qui pointe le même code ou la même zone que la vraie personne sur lore.kernel. J’aimerais demander à quelqu’un qui comprend mieux Claude : y a-t-il vraiment un passage qui identifie la cause réelle ?
    • Jusqu’aux alentours de 2000, les gens qui utilisaient un téléphone portable en public étaient vus comme des frimeurs insupportables ; l’IA traverse aujourd’hui une phase de rejet désagréable assez similaire
      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 malloc avec 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+mimalloc
    Il y a effectivement un problème intéressant ici, mais il n’aurait pas dû se manifester ainsi au départ

    • ripgrep, lorsqu’il est compilé avec musl en 64 bits, définit en réalité jemalloc comme allocateur global : https://github.com/BurntSushi/ripgrep/blob/435f59fc4b43af3ab...
    • D’après la pile de l’erreur de segmentation, l’allocation se produit dans opendir de 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 Rust
      Cela dit, si l’on doit passer par un allocateur utilisant un verrou global, il vaudrait sans doute mieux que ripgrep évite d’utiliser opendir de la libc
    • C’est un bug du noyau. Je suis d’accord pour dire que les allocateurs de libc sont médiocres sans grande raison, mais ce problème semble pouvoir se produire avec une probabilité similaire dans d’autres codes applicatifs, y compris avec mimalloc ou glibc
    • La plupart des programmes améliorent leur vitesse en réutilisant les allocations. Si l’on n’alloue pas du tout, on n’a pas besoin d’un allocateur rapide, et le travail de ripgrep n’exige pas intrinsèquement des allocations fréquentes
    • Au contraire, c’est grâce aux fonctionnalités de durcissement offertes par mallocng de musl que le bug du noyau a pu être découvert. Sans cela, il aurait peut-être corrompu silencieusement la mémoire pendant des mois sans se faire remarquer
  • Si 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

    • Ce n’est pas un cluster HPC, seulement btrfs sur ma station de travail
    • Je me suis demandé si la cause profonde de l’instabilité récente de GitHub n’était pas similaire. On voit soudain apparaître des milliards d’opérations sur de petits fichiers, amplifiées par l’usage de l’IA, et le graphe d’objets est intrinsèquement fragmenté, ce qui rend difficile de précharger une page puis de faire en sorte qu’une opération Git typique ne touche que les objets de cette page
      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

    • J’ai essayé de lire, mais j’ai abandonné au premier paragraphe Headline
      On 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 munmap concurrent »
      Difficile de comprendre ce qu’est le backing d’une page, ce que signifient freshly-faulted ou « 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 autres
      Voici un exemple de vraie documentation technique : https://yifan.lu/2019/01/11/the-first-f00d-exploit/
    • Le passage « les dépassements débordent toujours, les use-after-free utilisent toujours après libération, et la course du masque musl reste une course » m’a fait rire, on dirait de la poésie informatique
      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
    • Lire un rapport de bug généré par IA est horrible
    • Le traçage lui-même est excellent, mais l’explication n’a aucun sens. Un flush TLB supplémentaire ne peut pas être une erreur ; le CPU peut flusher quand il le veut. La vraie erreur semble être la présence d’une PTE de zero page à un moment où elle n’aurait pas dû exister
      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
    • C’est typiquement une analyse verbeuse de résidus de LLM. Elle est peut-être correcte, mais elle est pénible à lire en détail ; si un humain avait produit la même analyse, il en aurait réduit la longueur d’un facteur cinq
  • Pourquoi le bug ne se produit-il qu’avec musl libc, et pas avec les autres libc ?

    • C’est seulement une coïncidence, et cela ne se produit d’ailleurs que sur une seule machine
    • C’est probablement parce que l’allocateur de musl expose directement à l’application une seule page venant tout juste de provoquer un défaut de page. Les autres allocateurs préallouent généralement plusieurs pages à la fois, ce qui réduit la fenêtre temporelle de la condition de course
  • 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