1 Punkte von GN⁺ 3 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Das x86_64-unknown-linux-musl-Binary von Ripgrep 15.2.0 beendet sich beim Durchsuchen großer Dateibäume mit hoher Parallelität sporadisch mit SIGSEGV
  • Der Absturz tritt innerhalb von calloc auf, das von opendir aufgerufen wurde; an der Spitze des Stacktraces erscheint eine Integritätsprüfung der Heap-Metadaten von musl mallocng
  • Die Reproduktionsumgebung besteht aus einem Baum mit etwa 20 GiB und 1,8 Mio. Dateien; wiederholt wird mit rg nach einem nicht vorhandenen String gesucht
  • Auf einem 24-Core-System tritt das Problem in der Regel nach etwa 1 Minute auf, wenn genügend RAM vorhanden ist, damit der Suchbaum in den Kernel-Block-Cache passt
  • Es wurde unabhängig sowohl mit dem in OpenAI Codex enthaltenen rg als auch mit einem byteweise identischen Binary aus dem offiziellen Release reproduziert und damit als nicht von Codex-Abhängigkeiten abhängig bestätigt

Umgebung

  • Verwendete Version ist ripgrep 15.2.0 rev e89fff8 mit +pcre2-Feature
    • SIMD zur Compile-Zeit: +SSE2,-SSSE3,-AVX2
    • SIMD zur Laufzeit: +SSE2,+SSSE3,+AVX2
    • PCRE2 10.45 und JIT sind verfügbar
  • Betriebssystem ist OpenSUSE Tumbleweed Linux x86_64
  • Das zuerst entdeckte, mit OpenAI Codex gebündelte rg ist byteweise identisch mit dem offiziellen x86_64-unknown-linux-musl-Release
  • Unabhängig von Codex wurde es auch mit dem offiziellen Binary reproduziert; das Analyse-Binary wurde mit folgendem Befehl inklusive Debug-Symbolen gebaut
    • CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl

Reproduktionsschritte

  • generate_repro_tree.py erzeugt einen zufälligen Dateibaum, der die Statistiken des Repositorys nachbildet, in dem das Problem ursprünglich auftrat
    • Dieses Programm wurde mit einem LLM geschrieben
    • Das Ergebnis hat einen Umfang von etwa 20 GiB und 1,8 Mio. Dateien
  • Vom Root des erzeugten Baums aus wird wiederholt nach einem beliebigen nicht vorhandenen String gesucht
    • while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
  • Ein ausreichend großer Suchbaum wurde als wesentlich für die Reproduktion beobachtet
  • Auf einem 24-Core-System mit genügend freiem RAM, damit der gesamte Baum in den Kernel-Block-Cache passt, stürzt es in der Regel nach etwa 1 Minute ab

Absturzstelle

  • Das tatsächliche Ergebnis ist ein SIGSEGV mit Core-Dump
  • An der Spitze des Stacktraces steht get_meta aus musl mallocng; der Absturz erfolgt an einer Integritätsprüfung der Heap-Metadaten
  • Der Aufruffluss verläuft von opendir, das calloc aufruft, weiter zur Verzeichnisdurchsuchung der Rust-Standardbibliothek und zu den ripgrep-Workern von ignore::walk
    • get_meta__malloc_allzeropcallocopendir
    • std::fs::read_dirignore::walk::Work::read_dirignore::walk::Worker::run
  • Als Analyseunterlagen sind ein Core-Dump und das entsprechende rg-Binary angehängt

Erwartetes Verhalten und aktueller Stand

  • Erwartet wird, dass es auch bei groß angelegten Suchen mit hoher Parallelität ohne Segmentierungsfehler läuft
  • Die bereitgestellten Informationen enthalten keine gesicherte Ursache, keinen Fix, keine Review-Ergebnisse und keinen finalen Lösungsstatus

1 Kommentare

 
GN⁺ 3 시간 전
Hacker-News-Kommentare
  • Im Kernel-Patch gibt es eine interessante Stelle: https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...

    Ich habe einen interessanten Bugreport zu ripgrep und eine gewissenhafte, aber ziemlich schlechte KI-generierte Analyse gesehen
    Gemeint ist https://github.com/dfoxfranke/ripgrep-3494-analysis, und ich fand ebenfalls, dass er zu lang ist, um plausibel von einem Menschen geschrieben zu sein. Außerdem scheint dieser Thread erst heute erschienen zu sein

    • Es war eine Qual zu lesen, und ich kann in diesem ausufernden Text nirgends eine Stelle finden, die denselben Code oder Bereich benennt wie die reale Person auf lore.kernel. Ich würde gern jemanden fragen, der Claude besser versteht: Gibt es dort überhaupt eine Passage, die die tatsächliche Ursache identifiziert?
    • Bis etwa zum Jahr 2000 wurden Leute, die in der Öffentlichkeit Mobiltelefone benutzten, wie wichtigtuerische Unsympathen behandelt, und KI durchläuft derzeit eine ähnliche unangenehme Ablehnungsphase
      Vor zwei Jahren wäre so eine Analyse als großzügige Zeitspende an die Community gesehen worden, aber jetzt, da man weiß, dass Quelle und Token-Kosten nur 0,06 Dollar betragen, will man sie nicht lesen. Dass sie auch einen Vorgeschmack auf das gibt, was kommt, trägt ebenfalls dazu bei
      In ein paar Jahren wird es das letzte Mittel sein, wenn Menschen Bugs selbst untersuchen, und andere KI-Agenten werden die Berichte lesen und die Fixes verifizieren. Wie bei vom Compiler erzeugtem Assembler und Menschen, die in der Öffentlichkeit telefonieren, dürfte es von der Spottphase in die Gleichgültigkeitsphase übergehen
  • Ich verstehe, dass man den Standard-Allocator von musl aus Bequemlichkeit übernimmt, aber bei einer Anwendung, deren Zweck Geschwindigkeit ist, ist es seltsam, nicht auf einen schnelleren Allocator zu wechseln
    mallocng ist anfällig für Contention bei mehreren Threads. Selbst Anwendungen, die normalerweise I/O-gebunden waren, bekamen bei einem Build mit musl schon mit 8 Threads einen malloc-Flaschenhals; nach dem Wechsel zu mimalloc stieg die Performance um den Faktor 20, lag sehr nahe an der glibc-Standardkonfiguration und war nur etwas langsamer als glibc+mimalloc
    Es gibt hier tatsächlich ein interessantes Problem, aber es hätte sich ursprünglich nicht auf diese Weise zeigen sollen

    • ripgrep setzt beim Build mit 64-Bit-musl tatsächlich jemalloc als globalen Allocator: https://github.com/BurntSushi/ripgrep/blob/435f59fc4b43af3ab...
    • Wenn man sich den Stack des Segmentation Faults ansieht, passiert die Allokation in opendir der musl libc. Die Art, wie Rust den Allocator austauscht, ersetzt nicht den Allocator des gesamten Prozesses, sondern nur den Allocator, den Rust-Code aufruft
      Wenn man allerdings durch einen Allocator mit globalem Lock muss, scheint es besser, wenn ripgrep die Nutzung von opendir aus libc vermeidet
    • Das ist ein Kernel-Bug. Ich stimme zwar zu, dass libc-Allocatoren oft ohne guten Grund miserabel sind, aber dieses Problem scheint mit ähnlicher Wahrscheinlichkeit auch in anderem Anwendungscode aufzutreten, einschließlich mimalloc oder glibc
    • Die meisten Programme erhöhen ihre Geschwindigkeit, indem sie Allokationen wiederverwenden. Wenn man gar nicht allokiert, braucht man auch keinen schnellen Allocator, und die eigentliche Arbeit von ripgrep erfordert nicht inhärent häufige Allokationen
    • Gerade dank der Hardening-Funktionen von musls mallocng konnte der Kernel-Bug entdeckt werden. Andernfalls hätte er möglicherweise monatelang still Speicher beschädigt, ohne aufzufallen
  • Wenn man ripgrep auf einem HPC-Cluster gegen ein großes Cluster-Dateisystem laufen lässt, sollte man sofort aufhören und den Workflow neu entwerfen. Solche Aufgaben erzeugen massenhaft kleine I/O-Operationen, und das ist die Achillesferse großer Cluster-Dateisysteme
    Man verlagert Arbeit, die in der High-Bandwidth-Memory-Schicht des Clusters erledigt werden sollte, in die Metadaten-Schicht des Dateisystems, und schon wenige Nutzer, die das gleichzeitig tun, können ein gesamtes High-Bandwidth-Dateisystem lahmlegen

    • Das ist kein HPC-Cluster, sondern nur btrfs auf meiner Workstation
    • Ich habe mich gefragt, ob die jüngste Instabilität von GitHub eine ähnliche Grundursache haben könnte. Plötzlich gibt es Milliarden kleiner Dateioperationen, verstärkt durch KI-Nutzung, und der Objektgraph ist inhärent fragmentiert, sodass es schwer ist, eine Seite vorab zu laden und dann dafür zu sorgen, dass normale Git-Operationen nur Objekte innerhalb dieser Seite berühren
      Wenn an https://isolveproblems.substack.com/p/how-microsoft-vaporize... auch nur etwas dran ist, könnte schon ein einziger nicht optimierter Pfad in Azures Dateisystem-Abstraktion bei einem Nutzungsschub einen enormen Ausfallradius erzeugen
  • Es könnte besser sein, die Analyse des Kernel-Bugs direkt zu verlinken: https://github.com/dfoxfranke/ripgrep-3494-analysis

    • Ich habe versucht, sie zu lesen, aber beim ersten Headline-Absatz aufgegeben
      Da stehen Sätze wie: „Ein von einem Thread auf einer frisch per Page Fault angelegten anonymen Seite gespeicherter Wert verschwindet beim erneuten Lesen durch denselben Thread etwa 10 Instruktionen später, und das Backing der Seite wird während der Funktionsausführung ersetzt“, „liest man pagemap im Moment des Faults, ist das Backing die Zero Page des Kernels“, „der Mechanismus lässt sich auf die Interaktion zwischen dem schnellen anonymen Fault-Pfad pro VMA-Lock und dem TLB-Shootdown eines gleichzeitigen munmap localizen“
      Es ist schwer zu verstehen, was das backing einer Seite sein soll, was freshly-faulted oder „etwa 10 Instruktionen später“ bedeutet und wie ein Mechanismus „localized“ wird. Es wirkt eher wie aneinandergereihte Wörter als wie eine technische Erklärung
      Ein Beispiel für eine ordentliche technische Dokumentation ist: https://yifan.lu/2019/01/11/the-first-f00d-exploit/
    • Die Stelle „Overflows overflowen weiterhin, Use-after-free bleibt Use-after-free, und das musl-Masken-Race bleibt ein Race“ klingt wie Computerlyrik und ist lustig
      Das Fazit scheint grob zu sein, dass die Kombination aus Linux 7.0 und musl 1.2.5 problematisch ist, aber die Reproduktion gelingt weiterhin nur sporadisch unter starker Last auf derselben physischen Threadripper-CPU, daher konnte ein Hardwareproblem nicht ausgeschlossen werden
    • KI-generierte Bugreports zu lesen ist furchtbar
    • Das Tracing selbst ist hervorragend, aber die Erklärung ergibt keinen Sinn. Zusätzliche TLB-Flushes können kein Fehler sein; die CPU darf jederzeit flushen. Der eigentliche Fehler scheint darin zu liegen, dass eine Zero-Page-PTE existierte, obwohl sie dort nicht hätte sein dürfen
      Es sieht nach einem komplexen Race Condition aus, das bei einer CPU-Migration zu einem ungünstigen Zeitpunkt auftritt, oder nach einem Bug, bei dem der Pfad zum Entfernen von Page Tables vorübergehend eine falsche PTE freilegt. Ich glaube auch nicht, dass die PFN der Zero Page 0 ist
      Meine Vermutung wäre, dass der direkte Page-Table-Removal-Prozess der CPU erlaubt hat, über gecachte Einträge höherer Paging-Strukturen eine bereits freigegebene und wiederverwendete Tabelle zu lesen. Ich habe so etwas früher einmal debuggt, und es war wirklich schrecklich
    • Eine typisch ausufernde LLM-Abfallanalyse. Sie könnte richtig sein, aber sie ist schwer im Detail zu lesen; hätte ein Mensch dieselbe Analyse gemacht, wäre sie auf ein Fünftel gekürzt worden
  • Warum tritt der Bug nur bei musl libc auf und nicht bei anderen libcs?

    • Es ist bloßer Zufall und tritt zudem nur auf einer einzigen Maschine auf
    • Wahrscheinlich, weil der musl-Allocator eine einzelne frisch per Page Fault angelegte Seite direkt der Anwendung aussetzt. Andere Allocator allokieren normalerweise mehrere Seiten auf einmal vor, wodurch das Zeitfenster für die Race Condition kleiner wird
  • Normalerweise hätte ich die Thread-Stack-Größe von musl verdächtigt; ich frage mich, ob es als Kernel-Bug bestätigt wurde