Seit 2016 sind etwa 86% aller Sicherheitslücken auf Android speichersicherheitsbezogen. 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 die CFI von LLVM ist die Kompilierung mit Link-Time Optimization (LTO) erforderlich. Bei LTO wird die LLVM-Bitcode-Darstellung von Objektdateien bis zur Link-Zeit beibehalten. So kann der Compiler besser nachvollziehen, welche Optimierungen vorgenommen werden können. Durch Aktivieren von LTO wird die Größe der endgültigen Binärdatei reduziert und die Leistung verbessert, die Kompilierungszeit wird jedoch verlängert. Bei Tests unter Android führt die Kombination aus LTO und CFI zu einem vernachlässigbaren Mehraufwand in Bezug 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 für Arm64-Geräte standardmäßig für die Komponenten in /platform/build/target/product/cfi-common.mk
aktiviert.
Sie ist auch direkt in den Makefiles/Blueprint-Dateien einer Reihe von Media-Komponenten aktiviert, z. B. /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. CFI trägt dazu bei, Android-Nutzer zu schützen. Daher sollten Sie diese Funktion 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
So aktivieren Sie CFI in einer Make-Datei wie /platform/frameworks/av/cmds/stagefright/Android.mk
:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
LOCAL_SANITIZE
gibt CFI als Sanitizer während des Builds an.- Mit
LOCAL_SANITIZE_DIAG
wird der Diagnosemodus für CFI aktiviert. Im Diagnosemodus werden bei Abstürzen zusätzliche Debugging-Informationen in logcat ausgegeben. Das ist beim Entwickeln und Testen Ihrer Builds nützlich. Den Diagnosemodus sollten Sie jedoch aus Produktions-Builds entfernen. - Mit
LOCAL_SANITIZE_BLACKLIST
können Komponenten die CFI-Instrumentierung für einzelne Funktionen oder Quellcode-Dateien selektiv deaktivieren. Sie können eine Sperrliste als letzten Ausweg verwenden, um alle nutzerbezogenen Probleme zu beheben, die sonst auftreten könnten. Weitere Informationen finden Sie unter CFI deaktivieren.
CFI in Blueprint-Dateien unterstützen
Um CFI in einer Blueprint-Datei wie /platform/frameworks/av/media/libmedia/Android.bp
zu aktivieren, fügen Sie Folgendes hinzu:
sanitize: { cfi: true, diag: { cfi: true, }, blacklist: "cfi_blacklist.txt", },
Fehlerbehebung
Wenn Sie CFI in neuen Komponenten aktivieren, können einige Probleme mit Fehlern aufgrund von Typkonflikten bei Funktionen und Fehlern aufgrund von Typkonflikten bei Assembly-Code auftreten.
Fehler aufgrund von Funktionsinkompatibilitäten 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 schränkt virtuelle und nicht virtuelle Memberfunktionsaufrufe so ein, dass nur zu Objekten gesprungen werden kann, die eine abgeleitete Klasse des statischen Typs des Objekts sind, das für den Aufruf verwendet wird. Wenn Sie also Code haben, der gegen eine dieser Annahmen verstößt, wird die von CFI hinzugefügte Instrumentierung abgebrochen. Der Stacktrace enthält beispielsweise ein SIGABRT und logcat enthält eine Zeile, in der ein Mismatch bei der Integrität des Kontrollflusses gemeldet wird.
Um dieses Problem zu beheben, muss die aufgerufene Funktion denselben Typ haben, 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 Assembly-Code nicht typisiert ist, führt dies zu einem Typkonflikt.
Um dieses Problem zu beheben, erstellen Sie Wrapper für nativen Code für jeden Assembly-Aufruf und geben Sie den Wrappern dieselbe Funktionssignatur wie dem aufrufenden Pointer. Der Wrapper kann dann den Assembly-Code 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 Assembly-Funktionen 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 eine Angriffsfläche entsteht.
CFI deaktivieren
Wir haben keinen Leistungs-Overhead festgestellt. Sie müssen CFI also nicht deaktivieren. Wenn es jedoch Auswirkungen auf die Nutzer gibt, können Sie CFI für einzelne Funktionen oder Quellcode-Dateien selektiv deaktivieren, indem Sie zur Kompilierzeit eine Sanitizer-Blacklist-Datei angeben. Die Blacklist weist den Compiler an, die CFI-Instrumentierung an bestimmten Stellen zu deaktivieren.
Das Android-Build-System bietet Unterstützung für komponentenspezifische Blacklists (mit denen Sie Quelldateien oder einzelne Funktionen auswählen können, die keine CFI-Instrumentierung erhalten) für Make und Soong. Weitere Informationen zum Format einer Blacklist-Datei finden Sie in der Upstream-Clang-Dokumentation.
Zertifizierungsstufe
Derzeit gibt es keine CTS-Tests speziell für CFI. Stattdessen sollten Sie prüfen, ob die CTS-Tests mit oder ohne aktivierte CFI bestanden werden, um zu bestätigen, dass CFI das Gerät nicht beeinträchtigt.