Integrität des Kontrollflusses

Stand 2016 sind etwa 86% aller Sicherheitslücken unter Android auf 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. Die Kontrollflussintegrität (Control Flow Integrity, CFI) ist ein Sicherheitsmechanismus, der Änderungen am ursprünglichen Kontrollflussgraphen eines kompilierten Binärprogramms verhindert und so solche Angriffe erheblich 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. Die System-CFI ist standardmäßig aktiviert, Sie müssen jedoch die Kernel-CFI aktivieren.

Für die CFI von LLVM ist eine Kompilierung mit Link-Time Optimization (LTO) erforderlich. Bei LTO wird die LLVM-Bitcode-Darstellung von Objektdateien bis zur Linkzeit beibehalten. So kann der Compiler besser nachvollziehen, welche Optimierungen durchgeführt werden können. Wenn Sie LTO aktivieren, wird die Größe der finalen Binärdatei reduziert und die Leistung verbessert, aber die Kompilierungszeit erhöht. Bei Tests unter Android führt die Kombination aus LTO und CFI zu einem vernachlässigbaren Overhead bei Codegröße und Leistung. In einigen Fällen wurden beide verbessert.

Weitere technische Details zu CFI und zur Verarbeitung anderer Vorwärtssteuerungsüberprüfungen finden Sie in der LLVM-Designdokumentation.

Beispiele und Quelle

CFI wird vom Compiler bereitgestellt und fügt der Binärdatei während der Kompilierung Instrumentierung hinzu. Wir unterstützen CFI in der Clang-Toolchain und im Android-Buildsystem in AOSP.

CFI ist für Arm64-Geräte für die Komponenten in /platform/build/target/product/cfi-common.mk standardmäßig aktiviert. Außerdem ist er in den Makefiles/Blueprint-Dateien einiger Medienkomponenten wie /platform/frameworks/av/media/libmedia/Android.bp und /platform/frameworks/av/cmds/stagefright/Android.mk direkt aktiviert.

System-CFI implementieren

CFI ist standardmäßig aktiviert, wenn Sie Clang und das Android-Buildsystem verwenden. Da CFI dazu beiträgt, Android-Nutzer zu schützen, sollten Sie ihn nicht deaktivieren.

Wir empfehlen Ihnen dringend, die CFI für weitere 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.

Unterstützung von CFI in Makefiles

Wenn Sie CFI in einer Make-Datei wie /platform/frameworks/av/cmds/stagefright/Android.mk aktivieren möchten, fügen Sie Folgendes hinzu:

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.
  • LOCAL_SANITIZE_DIAG aktiviert den Diagnosemodus für CFI. Im Diagnosemodus werden bei Abstürzen zusätzliche Informationen zur Fehlerbehebung in logcat ausgegeben. Das ist nützlich bei der Entwicklung und dem Testen von Builds. Entfernen Sie den Diagnosemodus jedoch aus Produktionsbuilds.
  • Mit LOCAL_SANITIZE_BLACKLIST können Komponenten die CFI-Instrumentierung für einzelne Funktionen oder Quelldateien selektiv deaktivieren. Sie können eine Blacklist als letzten Ausweg verwenden, um Probleme zu beheben, die Nutzer sonst möglicherweise haben. Weitere Informationen finden Sie unter CFI deaktivieren.

Unterstützung von CFI in Blueprint-Dateien

Wenn Sie CFI in einer Blueprint-Datei wie /platform/frameworks/av/media/libmedia/Android.bp aktivieren möchten, 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 bei der Funktionstypübereinstimmung und Fehlern bei der Übereinstimmung des Assemblercodetyps auftreten.

Fehler vom Typ „Funktionstyp nicht übereinstimmend“ treten auf, weil CFI indirekte Aufrufe darauf beschränkt, nur zu Funktionen zu springen, die denselben dynamischen Typ wie der beim Aufruf verwendete statische Typ haben. CFI schränkt virtuelle und nicht virtuelle Aufrufe von Mitgliedsfunktionen darauf ein, nur zu Objekten zu springen, die eine abgeleitete Klasse des statischen Typs des Objekts sind, mit dem der Aufruf erfolgt. Wenn also Code gegen eine dieser Annahmen verstößt, wird die von CFI hinzugefügte Instrumentierung abgebrochen. Beispiel: Der Stack-Trace zeigt eine SIGABRT an und logcat enthält eine Zeile zur Integrität der Kontrollflusssteuerung, in der eine Abweichung festgestellt wird.

Um das Problem zu beheben, muss die aufgerufene Funktion denselben Typ haben, der statisch deklariert wurde. Hier sind zwei Beispiel-CLs:

Ein weiteres mögliches Problem ist der Versuch, CFI in Code zu aktivieren, der indirekte Aufrufe von Assembler enthält. Da Assemblycode nicht typisiert ist, führt dies zu einem Typfehler.

Um das Problem zu beheben, erstellen Sie für jeden Assembly-Aufruf Wrapper aus nativem Code und weisen Sie den Wrappern dieselbe Funktionssignatur zu wie dem Aufruf-Pointer. Der Wrapper kann dann den Assembly-Code direkt aufrufen. Da direkte Verzweigungen nicht von CFI instrumentiert werden (sie können nicht zur Laufzeit neu ausgerichtet werden und stellen daher kein Sicherheitsrisiko dar), wird das Problem dadurch behoben.

Wenn es zu viele Assembly-Funktionen gibt und nicht alle korrigiert werden können, können Sie auch alle Funktionen auf die schwarze Liste setzen, die indirekte Aufrufe an Assembly enthalten. Dies wird nicht empfohlen, da dadurch CFI-Prüfungen für diese Funktionen deaktiviert werden und sich die Angriffsfläche öffnet.

CFI deaktivieren

Wir haben keinen Leistungsoverhead festgestellt. Sie müssen CFI also nicht deaktivieren. Wenn sich die Funktion jedoch auf die Nutzer auswirkt, können Sie CFI selektiv für einzelne Funktionen oder Quelldateien deaktivieren, indem Sie zur Kompilierungszeit eine Blacklist-Datei für die Sanitizer bereitstellen. Die Blacklist weist den Compiler an, die CFI-Instrumentierung an den angegebenen Stellen zu deaktivieren.

Das Android-Buildsystem unterstützt Komponenten-Blacklists sowohl für Make als auch für Soong. So können Sie Quelldateien oder einzelne Funktionen auswählen, die nicht CFI-instrumentiert werden. Weitere Informationen zum Format einer Blacklist-Datei finden Sie in den Clang-Dokumenten.

Zertifizierungsstufe

Derzeit gibt es keine CTS-Tests speziell für CFI. Achten Sie stattdessen darauf, dass die CTS-Tests mit oder ohne aktivierter CFI bestehen, um sicherzustellen, dass die CFI keine Auswirkungen auf das Gerät hat.