Kontrollflussintegrität

Im Jahr 2016 hängen etwa 86 % aller Schwachstellen auf Android mit der Speichersicherheit zusammen. Die meisten Schwachstellen werden von Angreifern ausgenutzt, die den normalen Kontrollfluss einer Anwendung ändern, um willkürliche böswillige Aktivitäten mit allen Berechtigungen der ausgenutzten Anwendung durchzuführen. Kontrollflussintegrität (CFI) ist ein Sicherheitsmechanismus, der Änderungen am ursprünglichen Kontrollflussdiagramm einer kompilierten Binärdatei nicht zulässt, wodurch die Durchführung solcher Angriffe erheblich erschwert wird.

In Android 8.1 haben wir die CFI-Implementierung von LLVM im Medienstapel 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.

Das CFI von LLVM erfordert die Kompilierung mit Link-Time Optimization (LTO) . LTO behält die LLVM-Bitcode-Darstellung von Objektdateien bis zur Verbindungszeit bei, sodass der Compiler besser entscheiden kann, welche Optimierungen durchgeführt werden können. Die Aktivierung von LTO reduziert die Größe der endgültigen Binärdatei und verbessert die Leistung, erhöht jedoch die Kompilierungszeit. Beim Testen auf Android führt die Kombination von LTO und CFI zu einem vernachlässigbaren Overhead bei Codegröße und Leistung; in einigen Fällen besserte sich beides.

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

Beispiele und Quelle

CFI wird vom Compiler bereitgestellt und fügt der Binärdatei während der Kompilierungszeit Instrumentierung hinzu. Wir unterstützen CFI in der Clang-Toolchain und das Android-Build-System in AOSP.

CFI ist standardmäßig für Arm64-Geräte für den Komponentensatz 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. /platform/frameworks/av/media/libmedia/Android.bp und /platform/frameworks/av/cmds/stagefright/Android.mk .

Implementierung des CFI-Systems

CFI ist standardmäßig aktiviert, wenn Sie Clang und das Android-Build-System verwenden. Da CFI zur Sicherheit von Android-Benutzern beiträgt, sollten Sie es nicht deaktivieren.

Tatsächlich empfehlen wir Ihnen dringend, CFI für zusätzliche Komponenten zu aktivieren. Ideale Kandidaten sind privilegierter nativer Code oder nativer Code, der nicht vertrauenswürdige Benutzereingaben verarbeitet. Wenn Sie Clang und das Android-Build-System verwenden, können Sie CFI in neuen Komponenten aktivieren, indem Sie ein paar Zeilen zu Ihren Makefiles oder Blueprint-Dateien hinzufügen.

Unterstützung von CFI in Makefiles

Um CFI in einer Make-Datei wie /platform/frameworks/av/cmds/stagefright/Android.mk zu aktivieren, 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. Der Diagnosemodus gibt bei Abstürzen zusätzliche Debug-Informationen in Logcat aus, was beim Entwickeln und Testen Ihrer Builds hilfreich ist. Stellen Sie jedoch sicher, dass der Diagnosemodus bei Produktions-Builds entfernt wird.
  • LOCAL_SANITIZE_BLACKLIST ermöglicht Komponenten, die CFI-Instrumentierung für einzelne Funktionen oder Quelldateien selektiv zu deaktivieren. Als letzten Ausweg können Sie eine Blacklist verwenden, um etwaige benutzerbezogene Probleme zu beheben. Weitere Einzelheiten finden Sie unter Deaktivieren von CFI .

Unterstützung von CFI in Blueprint-Dateien

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, kann es zu einigen Problemen mit Fehlern bei der Nichtübereinstimmung von Funktionstypen und Fehlern bei der Nichtübereinstimmung von Assemblercodetypen kommen.

Funktionstypkonfliktfehler treten auf, weil CFI indirekte Aufrufe darauf beschränkt, nur zu Funktionen zu springen, die denselben dynamischen Typ haben wie der im Aufruf verwendete statische Typ. CFI beschränkt virtuelle und nicht virtuelle Member-Funktionsaufrufe darauf, nur zu Objekten zu springen, die eine abgeleitete Klasse des statischen Typs des Objekts sind, das für den Aufruf verwendet wurde. Das heißt, wenn Sie Code haben, der gegen eine dieser Annahmen verstößt, wird die von CFI hinzugefügte Instrumentierung abgebrochen. Der Stack-Trace zeigt beispielsweise ein SIGABRT an und Logcat enthält eine Zeile über die Kontrollflussintegrität, bei der eine Nichtübereinstimmung festgestellt wird.

Um dieses Problem zu beheben, stellen Sie sicher, dass die aufgerufene Funktion denselben Typ hat, der statisch deklariert wurde. Hier sind zwei Beispiel-CLs:

Ein weiteres mögliches Problem besteht darin, CFI in Code zu aktivieren, der indirekte Aufrufe der Assembly enthält. Da der Assemblercode nicht typisiert ist, führt dies zu einer Typinkongruenz.

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

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

CFI deaktivieren

Wir haben keinen Leistungsaufwand festgestellt, daher sollte es nicht nötig sein, CFI zu deaktivieren. Wenn es jedoch Auswirkungen auf den Benutzer gibt, können Sie CFI selektiv für einzelne Funktionen oder Quelldateien deaktivieren, indem Sie zur Kompilierungszeit eine Sanitizer-Blacklist-Datei bereitstellen. Die Blacklist weist den Compiler an, die CFI-Instrumentierung an bestimmten Orten zu deaktivieren.

Das Android-Build-System bietet Unterstützung für komponentenspezifische Blacklists (so dass Sie Quelldateien oder einzelne Funktionen auswählen können, die keine CFI-Instrumentierung erhalten) sowohl für Make als auch für Soong. Weitere Einzelheiten zum Format einer Blacklist-Datei finden Sie in den Upstream-Clang-Dokumenten .

Validierung

Derzeit gibt es keinen CTS-Test speziell für CFI. Stellen Sie stattdessen sicher, dass CTS-Tests mit oder ohne aktiviertem CFI bestanden werden, um sicherzustellen, dass CFI keine Auswirkungen auf das Gerät hat.