- 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 mitVM_SEALED, und der Kernel verweigert Änderungen übermprotect,munmap,mmap(MAP_FIXED),mremapsowie einige destruktivemadvise-Pfade- Während
memfd_createundmemfd_secreteher 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 permunmap/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_createundmemfd_secretgehören eher zur Familie des File Sealing- Sie können RAM-basierte anonyme Dateien erzeugen
memfd_secreterlaubt 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
msealist 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)startundlenbezeichnen Anfang und Länge eines gültigen VMA-Bereichs, der versiegelt werden solllenmuss korrekt seitenbündig ausgerichtet seinflagswird derzeit nicht verwendet und muss0sein
- In Linux 6.12 ruft die Implementierung
do_msealaufcurrent->mmzeigt auf diemm_struct, also den gesamten virtuellen Adressraum des aufrufenden Prozessesmmapinmm_structenthält die Liste dervm_area_struct, also zusammenhängender Speicherbereiche, die permmapangelegt wurden- Auch Bereiche wie Stack oder VDSO können als einzelne VMA dargestellt werden
do_msealberechnet das Endadresse des Bereichs und sperrt dann den Speicherbereich mitmmap_write_lock_killablecheck_mm_sealdurchläuft jede VMA des Zielbereichs und prüft zuerst die Grenzgültigkeit- Befindet sich in der Mitte des Bereichs nicht zugewiesener Speicher, wird
-ENOMEMzurückgegeben - Diese erste Prüfung verhindert, dass im Fehlerfall nur ein Teil der VMAs versiegelt wird
- Befindet sich in der Mitte des Bereichs nicht zugewiesener Speicher, wird
apply_mm_sealdurchläuft anschließend denselben Bereich erneut und setzt permseal_fixupdas FlagVM_SEALEDauf die Ziel-VMAs
Nach der Versiegelung verbotene Speicheroperationen
- Das Kernel-Patchset ergänzt Prüfungen auf
VM_SEALEDinmm/madvise.c,mm/mmap.c,mm/mprotect.c,mm/mremap.cundmm/mseal.c mprotectundpkey_mprotectrufen intern inmprotect_fixupcan_modify_vmaauf und geben-EPERMzurück, wenn die VMA versiegelt ist- In versiegelten VMAs sind folgende Operationen nicht erlaubt
- Änderung von Berechtigungsbits über
mprotectoderpkey_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) madvisemit einigen destruktiven Flags
- Änderung von Berechtigungsbits über
- Bei einer Verschiebung per
mremapwird sowohl auf der Quell- als auch auf der Ziel-VMA eine Versiegelungsprüfung durchgeführt - Falls
MREMAP_DONTUNMAPnicht gesetzt ist, wird die Quell-VMA entmappt; auch dabei muss diemunmap-Versiegelungsprüfung bestanden werden - Unter Linux 6.10 und neuer kann
msealper direktem Syscall-Aufruf verwendet werden; ein Beispiel-Wrapper nutzt die Syscall-Nummer462undflags=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
msealversiegelt, blockiert die Prüfung incan_modify_vmadenmprotect-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
msealversiegelt, kannmprotectdie tatsächlichen Rechte nicht ändern, und beim Ausführen des Shellcodes tritt ein Segmentation Fault auf
Einschränkungen bei Stack und Heap
- Wenn
msealab 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
brkaufrufen, um Speicher wieder freizugeben - Auf diesem Pfad kann über
arch_unmapunddo_vmi_unmapeine Verkleinerung erfolgen - Im versiegelten Zustand ist solches Unmapping nicht erlaubt, wodurch dynamische Speicherallokation kaputtgehen könnte
- Ein Heap-Allocator kann den Syscall
- 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
msealerneut 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
mmapanlegen und die Payload aus einem lesbaren Bereich hineinkopieren, allerdings mit deutlich mehr Aufwand
Abmilderung datenorientierter Exploits auf Basis von Unmapping
- Das Blockieren von
mprotectverhindert 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
msealkann 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
mallocundfreebei großen Chunks oberhalb vonM_MAP_THRESHOLDdirektmmapbzw.munmapaufrufen - 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
munmapbenachbarte Speicherbereiche entmappen - Dulins Beispiel zielt mit beliebigem
munmapauf die Bereiche.gnu.hashund.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
- Diese werden anschließend mit einem größeren
- Das verkürzte PoC allokiert zwei große Chunks, manipuliert das Größenfeld von
top[-1], entmappt perfree(top)auch benachbarte Bereiche und füllt sie mit einer größeren Allokation ausX-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
msealsoll 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
msealist 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
flagskö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
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.
Unter den vorgeschlagenen Flags gab es eines, mit dem man ein Seal ignorieren konnte, und Linus reagierte sinngemäß: „An einer Stelle wollt ihr
munmapverbieten, 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.
In einer Mail von Theo de Raadt an Jeff Xu antwortete er auf die Aussage, dass
mimmutable()undmseal()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, sondernexecve(), die libc-Initialisierung undld.sodafür zuständig werden.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.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.
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.
Außerdem interessiert mich, warum du
msealnicht für gerechtfertigt hältst und was stattdessen besser wäre.Kann man den
mseal-Systemaufruf perLD_PRELOAD-Trick überschreiben oder deaktivieren?msealein 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.
LD_PRELOADwird 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
msealfunktionieren, könnte der einfachste Weg sein, die Funktion „zu deaktivieren“.Allerdings können Programme, die
msealverwenden, prüfen, ob es tatsächlich funktioniert; ein kompromittierter Kernel müsste daher zusätzlich eine Methode haben,msealheimlich zu deaktivieren, damit die App-Prüfungen nach der Anwendung nicht anschlagen.Man könnte zum Beispiel die ausführbare Datei mit
ptraceverfolgen, Systemaufrufe überwachen undmseal(2)überspringen lassen.Dieser Systemaufruf ist nicht für ein Threat Model gedacht, in dem „der Angreifer bereits vor der Prozessinitialisierung Zugriff hatte“.
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
syscallselbst überschreiben.Meta: Der im Artikel angegebene Prototyp von
mseal()muss wohl korrigiert werden.Das erste Argument ist als
unsigned start addrangegeben, vermutlich ist aberunsigned long start_addrgemeint.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.
mimmutablewurde 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 aufshm_openangewiesen ist.