Seit 2016 sind etwa 86% aller Sicherheitslücken auf Android auf Probleme mit der Speichersicherheit zurückzuführen. Die meisten Sicherheitslücken werden von Angreifern ausgenutzt, indem sie den normalen Kontrollfluss einer App ändern, um beliebige schädliche Aktivitäten mit allen Berechtigungen der ausgenutzten App auszuführen. Control Flow Integrity (CFI) ist ein Sicherheitsmechanismus, der Änderungen am ursprünglichen Kontrollflussdiagramm einer kompilierten Binärdatei verhindert und dadurch solche Angriffe deutlich erschwert.
In Android 8.1 haben wir die LLVM-Implementierung von CFI im Media Stack aktiviert. In Android 9 haben wir CFI in weiteren Komponenten und auch im Kernel aktiviert. System-CFI ist standardmäßig aktiviert, Sie müssen jedoch Kernel-CFI aktivieren.
Für LLVM-CFI ist die Kompilierung mit Link-Time Optimization (LTO) erforderlich. LTO behält die LLVM-Bitcode-Darstellung von Objektdateien bis zur Link-Zeit bei, sodass der Compiler besser nachvollziehen kann, welche Optimierungen vorgenommen werden können. Durch die Aktivierung von LTO wird die Größe der endgültigen Binärdatei reduziert und die Leistung verbessert, die Kompilierungszeit jedoch verlängert. Bei Tests auf Android hat die Kombination aus LTO und CFI nur einen geringen Einfluss auf die Codegröße und Leistung. In einigen Fällen haben sich beide verbessert.
Weitere technische Details zu CFI und zur Verarbeitung anderer Vorwärtskontrollprüfungen finden Sie in der LLVM-Designdokumentation.
Beispiele und Quelle
CFI wird vom Compiler bereitgestellt und während der Kompilierung in die Binärdatei eingefügt. Wir unterstützen CFI in der Clang-Toolchain und im Android-Build-System in AOSP.
CFI ist standardmäßig für Arm64-Geräte für die Komponenten in
/platform/build/target/product/cfi-common.mk aktiviert.
Es ist auch direkt in den Makefiles/Blueprint
Dateien einer Reihe von Medienkomponenten aktiviert, z. B. in /platform/frameworks/av/media/libmedia/Android.bp
und /platform/frameworks/av/cmds/stagefright/Android.mk.
System-CFI implementieren
CFI ist standardmäßig aktiviert, wenn Sie Clang und das Android-Build-System verwenden. Da CFI dazu beiträgt, Android-Nutzer zu schützen, sollten Sie es nicht deaktivieren.
Wir empfehlen Ihnen sogar dringend, CFI für zusätzliche Komponenten zu aktivieren. Ideale Kandidaten sind privilegierter nativer Code oder nativer Code, der nicht vertrauenswürdige Nutzereingaben verarbeitet. Wenn Sie Clang und das Android-Build-System verwenden, können Sie CFI in neuen Komponenten aktivieren, indem Sie Ihren Makefiles oder Blueprint-Dateien einige Zeilen hinzufügen.
CFI in Makefiles unterstützen
Fügen Sie Folgendes hinzu, um CFI in einer Make-Datei wie /platform/frameworks/av/cmds/stagefright/Android.mk,
zu aktivieren:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
LOCAL_SANITIZEgibt CFI als Sanitizer während des Builds an.LOCAL_SANITIZE_DIAGaktiviert den Diagnosemodus für CFI. Im Diagnosemodus werden bei Abstürzen zusätzliche Debug-Informationen in logcat ausgegeben, was bei der Entwicklung und beim Testen Ihrer Builds hilfreich ist. Entfernen Sie den Diagnosemodus jedoch in Produktions-Builds.LOCAL_SANITIZE_BLACKLISTermöglicht es Komponenten, die CFI-Instrumentierung für einzelne Funktionen oder Quelldateien selektiv zu deaktivieren. Sie können eine Blacklist als letzten Ausweg verwenden, um alle Probleme zu beheben, die sonst für Nutzer auftreten könnten. Weitere Informationen finden Sie unter CFI deaktivieren.
CFI in Blueprint-Dateien unterstützen
Fügen Sie Folgendes hinzu, um CFI in einer Blueprint-Datei wie /platform/frameworks/av/media/libmedia/Android.bp,
zu aktivieren:
sanitize: {
cfi: true,
diag: {
cfi: true,
},
blacklist: "cfi_blacklist.txt",
},Fehlerbehebung
Wenn Sie CFI in neuen Komponenten aktivieren, können einige Probleme mit Fehlern bei der Funktionstyp-Nichtübereinstimmung und Fehlern bei der Assemblycode-Typ-Nichtübereinstimmung auftreten.
Fehler bei der Funktionstyp-Nichtübereinstimmung treten auf, weil CFI indirekte Aufrufe darauf beschränkt, nur zu Funktionen zu springen, die denselben dynamischen Typ wie der im Aufruf verwendete statische Typ haben. CFI beschränkt virtuelle und nicht virtuelle Memberfunktionsaufrufe darauf, nur zu Objekten zu springen, die eine abgeleitete Klasse des statischen Typs des Objekts sind, das zum Aufruf verwendet wird. Wenn Ihr Code eine dieser Annahmen verletzt, wird die von CFI hinzugefügte Instrumentierung abgebrochen. Beispiel: Der Stacktrace zeigt ein SIGABRT und logcat enthält eine Zeile, in der angegeben ist, dass CFI eine Nichtübereinstimmung gefunden hat.
Achten Sie darauf, dass die aufgerufene Funktion denselben Typ hat, der statisch deklariert wurde. Hier sind zwei Beispiel-CLs:
- Bluetooth: /c/platform/system/bt/+/532377
- NFC: /c/platform/system/nfc/+/527858
Ein weiteres mögliches Problem ist, dass Sie versuchen, CFI in Code zu aktivieren, der indirekte Aufrufe an Assembly enthält. Da Assemblycode nicht typisiert ist, führt dies zu einer Typ-Nichtübereinstimmung.
Erstellen Sie native Code-Wrapper für jeden Assemblyaufruf und geben Sie den Wrappern dieselbe Funktionssignatur wie dem aufrufenden Zeiger. Der Wrapper kann dann den Assemblycode direkt aufrufen. Da direkte Verzweigungen nicht von CFI instrumentiert werden (sie können zur Laufzeit nicht neu ausgerichtet werden und stellen daher kein Sicherheitsrisiko dar), wird das Problem dadurch behoben.
Wenn es zu viele Assemblyfunktionen gibt und nicht alle behoben werden können, können Sie auch alle Funktionen auf die Blacklist setzen, die indirekte Aufrufe an Assembly enthalten. Dies wird nicht empfohlen, da dadurch CFI-Prüfungen für diese Funktionen deaktiviert werden und somit die Angriffsfläche vergrößert wird.
CFI deaktivieren
Wir haben keine Leistungseinbußen festgestellt, daher sollten Sie CFI nicht deaktivieren müssen. Wenn es jedoch Auswirkungen auf Nutzer gibt, können Sie CFI selektiv für einzelne Funktionen oder Quelldateien deaktivieren, indem Sie zur Kompilierungszeit eine Sanitizer-Blacklist-Datei angeben. Die Blacklist weist den Compiler an, die CFI-Instrumentierung an bestimmten Stellen zu deaktivieren.
Das Android-Build-System unterstützt Blacklists pro Komponente (sodass Sie Quelldateien oder einzelne Funktionen auswählen können, die keine CFI-Instrumentierung erhalten) sowohl für Make als auch für Soong. Weitere Informationen zum Format einer Blacklist-Datei finden Sie in der Clang-Dokumentation.
Validierung
Derzeit gibt es keine CTS-Tests speziell für CFI. Achten Sie stattdessen darauf, dass CTS-Tests mit oder ohne aktiviertem CFI bestanden werden, um zu prüfen, ob CFI das Gerät beeinträchtigt.