Execute-Only-Speicher (XOM) für AArch64-Binärdateien

Ausführbare Codeabschnitte für AArch64-Systembinärdateien sind standardmäßig als „Nur ausführbar“ (nicht lesbar) gekennzeichnet, um eine Absicherung gegen Just-in-Time-Angriffe zur Wiederverwendung von Code zu gewährleisten. Code, der Daten und Code miteinander vermischt, und Code, der diese Abschnitte gezielt überprüft (ohne die Speichersegmente zunächst als lesbar zuzuordnen) funktioniert nicht mehr. Apps mit einem Ziel-SDK von 10 (API-Level 29 oder höher) sind betroffen, wenn die App versucht, Codeabschnitte von XOM-fähigen Systembibliotheken (Execute-Only Memory) im Speicher zu lesen, ohne den Abschnitt zuvor als lesbar zu markieren.

Um von dieser Schadensbegrenzung vollständig profitieren zu können, ist sowohl Hardware- als auch Kernel-Unterstützung erforderlich. Ohne diese Unterstützung könnten die Abhilfemaßnahmen möglicherweise nur teilweise durchgesetzt werden. Der gemeinsame Android 4.9-Kernel enthält die entsprechenden Patches, um dies auf ARMv8.2-Geräten vollständig zu unterstützen.

Implementierung

Vom Compiler generierte AArch64-Binärdateien gehen davon aus, dass Code und Daten nicht vermischt sind. Die Aktivierung dieser Funktion wirkt sich nicht negativ auf die Leistung des Geräts aus.

Für Code, der eine absichtliche Speicherintrospektion seiner ausführbaren Segmente durchführen muss, ist es ratsam, mprotect für die Codesegmente aufzurufen, die überprüft werden müssen, damit sie lesbar sind, und dann die Lesbarkeit zu entfernen, wenn die Überprüfung abgeschlossen ist.
Diese Implementierung führt dazu, dass Lesevorgänge in Speichersegmenten, die als Nur-Ausführen markiert sind, zu einem Segmentierungsfehler ( SEGFAULT ) führen. Dies kann auf einen Fehler, eine Sicherheitslücke, mit Code vermischte Daten (literales Pooling) oder eine absichtliche Selbstprüfung des Speichers zurückzuführen sein.

Geräteunterstützung und Auswirkungen

Geräte mit früherer Hardware oder früherem Kernel (niedriger als 4.9) ohne die erforderlichen Patches unterstützen diese Funktion möglicherweise nicht vollständig oder profitieren nicht davon. Geräte ohne Kernel-Unterstützung erzwingen möglicherweise keinen Benutzerzugriff auf den Nur-Ausführungs-Speicher. Kernel-Code, der explizit prüft, ob eine Seite lesbar ist, kann diese Eigenschaft jedoch dennoch erzwingen, z. B. process_vm_readv() .

Das Kernel-Flag CONFIG_ARM64_UAO muss im Kernel gesetzt sein, um sicherzustellen, dass der Kernel Userland-Seiten berücksichtigt, die als „Execute-Only“ gekennzeichnet sind. Frühere ARMv8-Geräte oder ARMv8.2-Geräte mit deaktiviertem User Access Override (UAO) profitieren möglicherweise nicht vollständig davon und können möglicherweise immer noch Ausführungsseiten mithilfe von Systemaufrufen lesen.

Vorhandenen Code umgestalten

Code, der von AArch32 portiert wurde, enthält möglicherweise gemischte Daten und Code, was zu Problemen führen kann. In vielen Fällen ist die Behebung dieser Probleme so einfach wie das Verschieben der Konstanten in einen .data Abschnitt in der Assemblydatei.

Handgeschriebene Assemblys müssen möglicherweise umgestaltet werden, um lokal gepoolte Konstanten zu trennen.

Beispiele:

Vom Clang-Compiler generierte Binärdateien sollten keine Probleme mit der Vermischung von Daten im Code haben. Wenn von der GNU-Compiler-Sammlung (GCC) generierter Code enthalten ist (aus einer statischen Bibliothek), überprüfen Sie die Ausgabebinärdatei, um sicherzustellen, dass Konstanten nicht in Codeabschnitten zusammengefasst wurden.

Wenn eine Code-Introspektion für ausführbare Codeabschnitte erforderlich ist, rufen Sie zunächst mprotect auf, um den Code als lesbar zu markieren. Nachdem der Vorgang abgeschlossen ist, rufen Sie mprotect erneut auf, um ihn als unlesbar zu markieren.

Aktivieren

„Nur ausführen“ ist standardmäßig für alle 64-Bit-Binärdateien im Build-System aktiviert.

Deaktivieren

Sie können „Nur ausführen“ auf Modulebene, für einen gesamten Unterverzeichnisbaum oder global für einen gesamten Build deaktivieren.

XOM kann für einzelne Module deaktiviert werden, die nicht umgestaltet werden können oder deren ausführbaren Code gelesen werden muss, indem die Variablen LOCAL_XOM und xom auf false gesetzt werden.

// Android.mk
LOCAL_XOM := false

// Android.bp
cc_binary { // or other module types
   ...
   xom: false,
}

Wenn der Nur-Ausführungs-Speicher in einer statischen Bibliothek deaktiviert ist, wendet das Build-System dies auf alle abhängigen Module dieser statischen Bibliothek an. Sie können dies überschreiben, indem Sie xom: true, verwenden.

Um den Nur-Ausführungs-Speicher in einem bestimmten Unterverzeichnis (z. B. foo/bar/) zu deaktivieren, übergeben Sie den Wert an XOM_EXCLUDE_PATHS .

make -j XOM_EXCLUDE_PATHS=foo/bar

Alternativ können Sie die Variable PRODUCT_XOM_EXCLUDE_PATHS in Ihrer Produktkonfiguration festlegen.

Sie können schreibgeschützte Binärdateien global deaktivieren, indem Sie ENABLE_XOM=false an Ihren make Befehl übergeben.

make -j ENABLE_XOM=false

Validierung

Für Nur-Ausführen-Speicher sind keine CTS- oder Verifizierungstests verfügbar. Sie können Binärdateien manuell überprüfen, indem Sie readelf verwenden und die Segmentflags überprüfen.