2 Punkte von GN⁺ 2024-10-27 | 1 Kommentare | Auf WhatsApp teilen
  • mseal in Linux 6.10 ist ein Syscall zur Exploit-Abmilderung, der virtuelle Speicherbereiche zur Laufzeit versiegelt, sodass ein Angreifer, der Ausführungsrechte erlangt hat, anschließend VMA-Berechtigungen oder das Layout nur schwer verändern kann
  • mseal(start, len, flags) versieht einen seitenbündig ausgerichteten VMA-Bereich mit VM_SEALED, und der Kernel verweigert Änderungen über mprotect, munmap, mmap(MAP_FIXED), mremap sowie einige destruktive madvise-Pfade
  • Während memfd_create und memfd_secret eher für RAM-basierte anonyme Dateien und Zugriffskontrolle auf geheimen Speicher gedacht sind, konzentriert sich mseal darauf, nach Codeausführung durch einen entfernten Angreifer Rechteänderungen, Unmapping und Remapping zu verhindern
  • Zu den typischen abzuwehrenden Angriffen gehören das Umgehen von NX mit mprotect(PROT_EXEC), um Shellcode auszuführen, sowie datenorientierte Exploits, bei denen per munmap/Remapping Löcher im Speicher erzeugt und anschließend mit Angreiferdaten gefüllt werden
  • Eine Integration in glibc ist ab 2.41 möglich, aber Stack und Heap sind wegen Laufzeit-Erweiterung und -Verkleinerung keine automatischen Versiegelungskandidaten, sodass Entwickler Zeitpunkt und Umfang des Einsatzes sorgfältig wählen müssen

Schutz durch mseal

  • Memory Sealing macht laufende VMA-Bereiche gegenüber späteren riskanten Änderungen nahezu unveränderlich
  • Selbst wenn ein Angreifer eine Codeausführungs-Primitive besitzt, ist es bei versiegelten VMAs schwer, per virtuellen Speicheroperationen Rechte zu ändern oder das Layout vorteilhaft zu manipulieren
  • Das Chrome-Security-Team führte diesen Syscall ein, um die V8-CFI-Strategie auf Linux-basiertem ChromeOS zu unterstützen
  • Nach mehreren Diskussions- und Überarbeitungsrunden wurde er in den Linux-Kernel aufgenommen und könnte sich durch die glibc-Integration auch außerhalb von Browsern verbreiten

Unterschied zur memfd-Familie

  • memfd_create und memfd_secret gehören eher zur Familie des File Sealing
    • Sie können RAM-basierte anonyme Dateien erzeugen
    • memfd_secret erlaubt den Zugriff auf den betreffenden Speicherbereich nur Prozessen, die den File Descriptor besitzen
    • Das kann für „Secure Enclave“-artige User-Space-Mappings zum Schutz sensibler In-Memory-Daten genutzt werden
  • mseal ist kein Syscall gegen lokale Angreifer, die sensible Informationen abziehen wollen, sondern auf Exploit-Abmilderung gegen entfernte Angreifer ausgelegt, die Codeausführung anstreben

Funktionsweise im Kernel

  • Die Syscall-Signatur lautet int mseal(unsigned long start, size_t len, unsigned long flags)
    • start und len bezeichnen Anfang und Länge eines gültigen VMA-Bereichs, der versiegelt werden soll
    • len muss korrekt seitenbündig ausgerichtet sein
    • flags wird derzeit nicht verwendet und muss 0 sein
  • In Linux 6.12 ruft die Implementierung do_mseal auf
    • current->mm zeigt auf die mm_struct, also den gesamten virtuellen Adressraum des aufrufenden Prozesses
    • mmap in mm_struct enthält die Liste der vm_area_struct, also zusammenhängender Speicherbereiche, die per mmap angelegt wurden
    • Auch Bereiche wie Stack oder VDSO können als einzelne VMA dargestellt werden
  • do_mseal berechnet das Endadresse des Bereichs und sperrt dann den Speicherbereich mit mmap_write_lock_killable
  • check_mm_seal durchläuft jede VMA des Zielbereichs und prüft zuerst die Grenzgültigkeit
    • Befindet sich in der Mitte des Bereichs nicht zugewiesener Speicher, wird -ENOMEM zurückgegeben
    • Diese erste Prüfung verhindert, dass im Fehlerfall nur ein Teil der VMAs versiegelt wird
  • apply_mm_seal durchläuft anschließend denselben Bereich erneut und setzt per mseal_fixup das Flag VM_SEALED auf die Ziel-VMAs

Nach der Versiegelung verbotene Speicheroperationen

  • Das Kernel-Patchset ergänzt Prüfungen auf VM_SEALED in mm/madvise.c, mm/mmap.c, mm/mprotect.c, mm/mremap.c und mm/mseal.c
  • mprotect und pkey_mprotect rufen intern in mprotect_fixup can_modify_vma auf und geben -EPERM zurück, wenn die VMA versiegelt ist
  • In versiegelten VMAs sind folgende Operationen nicht erlaubt
    • Änderung von Berechtigungsbits über mprotect oder pkey_mprotect
    • Unmapping per munmap
    • Ersetzen einer versiegelten Zuordnung durch eine veränderliche, unversiegelte Zuordnung via mmap(MAP_FIXED)
    • Vergrößerung oder Verkleinerung per mremap
    • Verschiebung zu einem neuen Ziel per mremap(MREMAP_MAYMOVE | MREMAP_FIXED)
    • madvise mit einigen destruktiven Flags
  • Bei einer Verschiebung per mremap wird sowohl auf der Quell- als auch auf der Ziel-VMA eine Versiegelungsprüfung durchgeführt
  • Falls MREMAP_DONTUNMAP nicht gesetzt ist, wird die Quell-VMA entmappt; auch dabei muss die munmap-Versiegelungsprüfung bestanden werden
  • Unter Linux 6.10 und neuer kann mseal per direktem Syscall-Aufruf verwendet werden; ein Beispiel-Wrapper nutzt die Syscall-Nummer 462 und flags=0

Blockieren der Shellcode-Ausführung durch stärkere NX-Durchsetzung

  • Auch wenn Angreifer Code-Reuse-Techniken wie ROP haben, bevorzugen sie mitunter dennoch die Ausführung von Shellcode
    • Sie platzieren Shellcode in einem nicht ausführbaren Stack oder Heap
    • Sie starten per Schwachstelle eine anfängliche ROP-Kette
    • Sie deaktivieren mit mprotect(PROT_EXEC) das NX-Bit für den Bereich mit dem Shellcode
    • Sie springen in diesen Bereich und führen den Shellcode aus
  • Ein Angriff auf den MikroTik-RouterOS-SMB-Daemon mit CVE-2018-7445 ist ein Beispiel für diesen Ablauf
    • Socket-basierter Shellcode wird in einen nicht ausführbaren Heap geschrieben
    • Eine durch Stack Overflow erzeugte ROP-Kette ändert die Rechte des Heap-Speichers
    • Danach wird der Shellcode ausgeführt
  • Wird die betreffende VMA mit mseal versiegelt, blockiert die Prüfung in can_modify_vma den mprotect-Aufruf und damit die Rechteänderung
  • Im Beispielcode ist die Ausführung nach mprotect(PROT_READ|PROT_WRITE|PROT_EXEC) möglich, wenn die Shellcode-Seite auf dem Stack nicht versiegelt wird
  • Wird dieselbe Seite mit mseal versiegelt, kann mprotect die tatsächlichen Rechte nicht ändern, und beim Ausführen des Shellcodes tritt ein Segmentation Fault auf

Einschränkungen bei Stack und Heap

  • Wenn mseal ab glibc 2.41 eingeführt wird, soll der dynamische Loader die Versiegelung auf eine vorab festgelegte Menge von VMAs anwenden
  • Nach aktuellem Plan werden Stack und Heap nicht automatisch versiegelt
  • Stack und Heap können sich zur Laufzeit vergrößern, sodass eine automatische Versiegelung die Anwendung beschädigen könnte
    • Ein Heap-Allocator kann den Syscall brk aufrufen, um Speicher wieder freizugeben
    • Auf diesem Pfad kann über arch_unmap und do_vmi_unmap eine Verkleinerung erfolgen
    • Im versiegelten Zustand ist solches Unmapping nicht erlaubt, wodurch dynamische Speicherallokation kaputtgehen könnte
  • Entwickler müssen im Anwendungskontext entscheiden, zu welchem Zeitpunkt und auf welche Bereiche die Versiegelung angewendet werden kann
  • Ein Beispiel-Makro versiegelt wiederholt die Seiten ausgewählter Stack-Frames, die potenziell nicht vertrauenswürdige Daten enthalten
  • Wird mseal erneut auf eine bereits versiegelte VMA angewendet, ist das ein No-op und erzeugt keinen Fehler
  • Funktionen wie automatisches Stack-Wachstum oder Stack Splitting erfordern besondere Vorsicht
  • Wenn ein Angreifer unbedingt Shellcode ausführen will, könnte er dennoch einen neuen ausführbaren Bereich per mmap anlegen und die Payload aus einem lesbaren Bereich hineinkopieren, allerdings mit deutlich mehr Aufwand

Abmilderung datenorientierter Exploits auf Basis von Unmapping

  • Das Blockieren von mprotect verhindert auch, dass ein versiegelter Bereich wieder beschreibbar gemacht wird, was beim Schutz von Datenvariablen hilft, deren Veränderung Exploit-Primitiven stärken könnte
  • Die Chrome-Maintainer betrachten Techniken, bei denen korrumpierte Pointer an Unmapping- und Remapping-Syscalls übergeben werden, um Löcher in den Speicher zu stanzen und diese anschließend mit angreiferkontrollierten Daten zu füllen
  • Dieser Ansatz verändert keine Control-Flow-Übergänge wie Stack-Rücksprungadressen oder Funktionspointer direkt und kann daher die Garantien von Forward-Edge- und Backward-Edge-CFI umgehen
  • In Browsern mit JIT-Compiler kann diese Technik besonders nützlich sein
    • V8s Turbofan kann Bereiche erzeugen, die zwischen RW und RX umgeschaltet werden
    • Ein Angreifer kann über JavaScript auf Hot Paths die Erzeugung von ausführbarem Code im JIT anstoßen
    • Durch das Füllen eines entmappten Bereichs können wichtige Daten überschrieben und dadurch Änderungen erzeugt werden, die letztlich zu Codeausführung führen
  • Es handelt sich um einen datenorientierten Exploit, der nicht direkt den Control Flow kapert oder Pointer-Leaks voraussetzt, sondern gezielt bestimmte Daten im Speicher manipuliert, um den Control Flow zu beeinflussen
  • mseal kann solche Hole-Punching-Szenarien verhindern, indem es Unmapping und Remapping blockiert

Der Fall House of Muney

  • House of Muney verwendet in User-Space-Heap-Exploits eine ähnliche Unmapping-basierte Technik
  • Qualys nutzte diese Technik in einem praktischen Exploit für einen alten Qmail-Bug
  • Die Methode beruht darauf, dass malloc und free bei großen Chunks oberhalb von M_MAP_THRESHOLD direkt mmap bzw. munmap aufrufen
  • Für große Chunks gibt es keine zwischengeschalteten Freelist-Caches, was den Exploit-Ablauf vereinfacht
  • Wenn die Größenmetadaten am oberen Ende eines Allokations-Chunks auf eine andere Seitengröße verfälscht und der Chunk anschließend freigegeben wird, kann munmap benachbarte Speicherbereiche entmappen
  • Dulins Beispiel zielt mit beliebigem munmap auf die Bereiche .gnu.hash und .dynsym
    • Diese werden anschließend mit einem größeren mmap-Chunk erneut gefüllt
    • Dadurch kann ein noch nicht aufgelöster PLT-Eintrag überschrieben werden
    • Ein Angriff im Stil eines GOT Overwrite wird damit wieder möglich
  • Das verkürzte PoC allokiert zwei große Chunks, manipuliert das Größenfeld von top[-1], entmappt per free(top) auch benachbarte Bereiche und füllt sie mit einer größeren Allokation aus X-Daten erneut
  • Diese Technik kann auch bei vorhandener CFI funktionieren und erfordert kein vorheriges ASLR-Leak
  • Die geplante VMA-Versiegelung in der glibc-Integration von mseal soll gemappte Binärcodes und dynamische Bibliotheken automatisch vor solchen Unmapping-/Remapping-Tricks schützen und den Angriff damit automatisch abmildern
  • Entwickler, die zusätzlichen Schutz wollen, können gezielt mmap-Allokationen versiegeln, die während der Programmlaufzeit weder erweitert noch entmappt werden

Künftiger Einsatzbereich

  • mseal ist eine vergleichsweise neue Abmilderungsfunktion im Linux-Kernel, und es könnte noch weitere Anwendungsfälle geben, die bislang nicht behandelt wurden
  • Wenn die glibc-Integration abgeschlossen und ausgereift ist, dürften weitere Anpassungen folgen, die besser zu den Anforderungen des Syscalls passen
  • Auch der derzeit ungenutzte Parameter flags könnte künftig einen konkreteren Zweck erhalten
  • Beim Einsatz in realer Software sollten Lebensdauer und Änderungsbedarf des zu versiegelnden Speichers gemeinsam bewertet werden

1 Kommentare

 
GN⁺ 2024-10-27
Hacker-News-Kommentare
  • Es wäre gut, wenn ein Insider zusammenfassen könnte, welche Einwände und Bedenken es in der heftigen Diskussion auf der Kernel-Mailingliste gab.
    Die Mailingliste selbst ist mir manchmal zu scharf im Ton, daher meide ich sie eher, und Kopfschmerzen habe ich schon genug.
    Der Mechanismus selbst wirkt vernünftig, aber es überrascht mich, dass es so eine Funktion im Kernel nicht schon gab.

    • Ich weiß nicht, ob es außer dem verlinkten Thread noch mehr gab, aber im Großen und Ganzen war es Linus’ direkte Art.
      Unter den vorgeschlagenen Flags gab es eines, mit dem man ein Seal ignorieren konnte, und Linus reagierte sinngemäß: „An einer Stelle wollt ihr munmap verbieten, aber an einer anderen das Seal ignorieren?“
      Danach sagte er noch deutlicher, dass „einmal versiegelt eben versiegelt ist“ und dass es nicht gehe, dass „manche Stellen das Seal beachten und irgendwelche anderen nicht“.
      Später sagte er sogar, Seals seien nicht ignorierbar und nicht verhandelbar; wer weiter vorschlage, ein Flag zum Ignorieren von Seals einzubauen, lande auf seiner ignore list.
    • https://lwn.net/ml/linux-kernel/7071.1697661373@cvs.openbsd....
      In einer Mail von Theo de Raadt an Jeff Xu antwortete er auf die Aussage, dass mimmutable() und mseal() zwar unterschiedliche Ansätze hätten, aber beide das Ziel verfolgten, Linux-Anwendungen sicherer zu machen, indem Speicher vor Angreifern versiegelt wird: „Es wirkt, als würdet ihr mseal nur für Chrome bauen.“
      Er meinte, es werde nicht gut zu Anwendungen allgemein passen, unter anderem weil es zu komplex sei und die Erfahrung mit mimmutable() zeige, dass Anwendungen das nicht selbst handhaben, sondern execve(), die libc-Initialisierung und ld.so dafür zuständig werden.
    • Auch Matthew Wilcox antwortete Jeff Xu scharf, sinngemäß: „Danke, dass du gezeigt hast, dass du nicht weißt, was verhindert werden muss.“
      Er stimmte Theo und Linus zu und hielt die Grundidee von mimmutable() zwar für gut, aber die Art, wie sie aufgeteilt und implementiert wurde, für furchtbar.
    • Dieser Artikel könnte hilfreich sein: https://lwn.net/Articles/948129/
  • Ich frage mich, wie man diesen Systemaufruf eigentlich verwendet.
    Chrome will ihn haben, aber weil ein Angreifer sonst mit anderen Flags neu mappen könnte, dürfen versiegelte Pages nicht ungemappt werden.
    Dann kann man ihn bei zur Laufzeit allozierten Pages doch praktisch nicht einsetzen, außer man will sie über die gesamte Lebensdauer des Prozesses behalten, oder?
    Wäre er damit nicht schwer auf sehr attraktive Ziele wie JS-Sandbox-Speicher anzuwenden?
    Ich frage mich, ob man solche Arbeiten in einem separaten Prozess ausführt, den Speicher versiegelt und den Prozess danach beendet.
    Ich kenne Chromes Speicher- und Prozessverwaltung nicht gut genug, um sicher zu sein, warum das kein Problem ist; und ich frage mich, ob das auch der Grund ist, warum oft erklärt wird, diese Funktion sei für die meisten Programme nicht nützlich.

    • Hier könnten mehrere Prozesse eine Option sein.
      Soweit ich weiß, nutzt Chrome das sehr umfassend, und aus Gründen wie Isolation über Namespaces braucht man ohnehin separate Prozesse; es könnte also in diese Richtung gehen.
  • Diskussion nach mseal(), 20. Oktober 2023: https://lwn.net/Articles/948129/
    mseal() rückt näher, 19. Januar 2024: https://lwn.net/Articles/958438/
    Speicherversiegelung für die GNU C Library, 12. Juni 2024: https://lwn.net/Articles/978010/

  • Es ist bedauerlich, dass das Betriebssystem einen solchen Aufruf implementieren muss, obwohl moderne x86_64-Architekturen viele Funktionen haben, die sicheres Programmieren und Computing unterstützen.
    Ich denke, dass der Versuch, alte Systeme weiterzuflicken, die auf veralteten Altlasten, Denkweisen und Paradigmen beruhen, die nicht zur heutigen Welt und zum heutigen Wissen passen, den Fortschritt der Informatik bremst und buchstäblich Milliarden Menschen gefährdet.
    Natürlich will ich damit nicht sagen, dass diese Änderung kein Schritt in die richtige Richtung ist; aber wenn man das alte Ideal loslässt, dass das Betriebssystem funktionieren müsse, und stattdessen heutige Systeme, heutiges Wissen und das berücksichtigt, was Menschen von Systemen erwarten, kann man sich Systeme vorstellen, die Entwickler und Nutzer von der heutigen Last und den heutigen Risiken befreien.
    Dass es Architektur-Bugs gibt, stimmt, aber Software nutzt die vorhandenen Funktionen nicht einmal richtig; Architektur-Bugs als Gegenargument anzuführen, geht daher am Kern vorbei.
    Wenn etwas auf wackligem Fundament gebaut ist, wird es immer billigere Wege zur Kompromittierung geben.

    • Mich würde interessieren, welche ungenutzten oder wenig genutzten Sicherheitsfunktionen x86_64 hat, die ohne eine Möglichkeit des Betriebssystems, sie zu verwenden oder zu aktivieren, nutzbar wären.
      Außerdem interessiert mich, warum du mseal nicht für gerechtfertigt hältst und was stattdessen besser wäre.
  • Kann man den mseal-Systemaufruf per LD_PRELOAD-Trick überschreiben oder deaktivieren?

    • Anders als die bisherigen Speicherschutzmechanismen in Linux ist mseal ein Systemaufruf, der eher auf die Abmilderung von Remote-Code-Execution-Angriffen abzielt als auf lokale Angreifer, die sensible Geheimnisse aus dem Speicher extrahieren wollen.
      Wenn ein Remote-Angreifer die lokale Umgebung verändern kann, sollte man davon ausgehen, dass er bereits in das System eingedrungen ist.
    • Mit LD_PRELOAD wird das vermutlich nicht funktionieren.
      Damit es wirkt, müsste es sich um eine importierte Funktion handeln; rohe Systemaufrufe lassen sich auf diese Weise nicht abfangen.
      Diskussion zum Abfangen von Linux-Systemaufrufen: https://stackoverflow.com/questions/69859/how-could-i-interc...
      Einen gepatchten Kernel selbst zu bauen, der nur so tut, als würde mseal funktionieren, könnte der einfachste Weg sein, die Funktion „zu deaktivieren“.
      Allerdings können Programme, die mseal verwenden, prüfen, ob es tatsächlich funktioniert; ein kompromittierter Kernel müsste daher zusätzlich eine Methode haben, mseal heimlich zu deaktivieren, damit die App-Prüfungen nach der Anwendung nicht anschlagen.
    • Wenn man früh im Prozess die Kontrolle hat, gibt es ziemlich viele Möglichkeiten, es zu überschreiben.
      Man könnte zum Beispiel die ausführbare Datei mit ptrace verfolgen, Systemaufrufe überwachen und mseal(2) überspringen lassen.
      Dieser Systemaufruf ist nicht für ein Threat Model gedacht, in dem „der Angreifer bereits vor der Prozessinitialisierung Zugriff hatte“.
    • Den Wrapper für den mseal-Aufruf kann man überschreiben, aber den Systemaufruf selbst nicht.
      Soweit ich gesehen habe, ändern alle PRELOAD-basierten Überschreibungen von Systemaufrufen nur den Wrapper; wenn man den Systemaufruf direkt ausführt, dürfte er sich nicht überschreiben lassen.
      Technisch könnte man möglicherweise die Funktion syscall selbst überschreiben.
    • Laut https://lwn.net/Articles/978010/ soll ein glibc Tunable hinzukommen.
  • Meta: Der im Artikel angegebene Prototyp von mseal() muss wohl korrigiert werden.
    Das erste Argument ist als unsigned start addr angegeben, vermutlich ist aber unsigned long start_addr gemeint.

    • Sieht inzwischen in Ordnung aus: int mseal(unsigned long start, size_t len, unsigned long flags)
  • In „Memory Sealing ‘Mseal’ System Call Merged for Linux 6.10“ (2024) https://news.ycombinator.com/item?id=40474510#40474551 wurde bereits diskutiert, wie CPython den Systemaufruf mseal() unterstützen sollte.

  • In OpenBSD gibt es diese Funktion schon seit Längerem: https://man.openbsd.org/mimmutable.2
    Ich frage mich, warum eine so naheliegende Funktion erst jetzt in Linux landet.

    • OpenBSDs mimmutable wurde mit OpenBSD 7.3 eingeführt, das am 10. April 2023 veröffentlicht wurde; „seit Längerem“ stimmt also nicht wirklich.
      Linux und FreeBSD haben dagegen schon lange memfd_create, während OpenBSD mangels anonymer Dateien auf shm_open angewiesen ist.