Kontrollflussintegrität

Seit 2016 beziehen sich etwa 86 % aller Schwachstellen auf Android auf die Speichersicherheit. Die meisten Schwachstellen werden von Angreifern ausgenutzt, die den normalen Kontrollfluss einer Anwendung ändern, um willkürliche böswillige Aktivitäten mit allen Privilegien der ausgenutzten Anwendung auszuführen. Control Flow Integrity (CFI) ist ein Sicherheitsmechanismus, der Änderungen am ursprünglichen Kontrollflussdiagramm einer kompilierten Binärdatei verbietet, wodurch die Durchführung solcher Angriffe erheblich erschwert wird.

In Android 8.1 haben wir die LLVM-Implementierung von CFI im Medienstapel aktiviert. In Android 9 haben wir CFI in mehr Komponenten und auch im Kernel aktiviert. System CFI ist standardmäßig aktiviert, aber Sie müssen 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, wodurch der Compiler besser beurteilen kann, welche Optimierungen durchgeführt werden können. Das Aktivieren von LTO reduziert die Größe der endgültigen Binärdatei und verbessert die Leistung, verlängert jedoch die Kompilierzeit. Beim Testen auf Android führt die Kombination von LTO und CFI zu einem vernachlässigbaren Mehraufwand für Codegröße und Leistung. in einigen Fällen verbesserte sich beides.

Weitere technische Details zu CFI und zur Handhabung anderer Forward-Control-Prüfungen finden Sie in der LLVM-Designdokumentation .

Beispiele und Quelle

CFI wird vom Compiler bereitgestellt und fügt der Binärdatei während der Kompilierzeit Instrumente 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 Satz von Komponenten in /platform/build/target/product/cfi-common.mk . Es ist auch direkt in 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 .

Implementierungssystem CFI

CFI ist standardmäßig aktiviert, wenn Sie Clang und das Android-Buildsystem 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-Buildsystem verwenden, können Sie CFI in neuen Komponenten aktivieren, indem Sie Ihren Makefiles oder Blueprint-Dateien ein paar Zeilen 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 Bereinigungsmittel während des Builds an.
  • LOCAL_SANITIZE_DIAG Diagnosemodus für CFI. Der Diagnosemodus gibt bei Abstürzen zusätzliche Debug-Informationen in Logcat aus, was beim Entwickeln und Testen Ihrer Builds nützlich ist. Stellen Sie jedoch sicher, dass Sie den Diagnosemodus auf Produktions-Builds entfernen.
  • LOCAL_SANITIZE_BLACKLIST ermöglicht es Komponenten, die CFI-Instrumentierung für einzelne Funktionen oder Quelldateien selektiv zu deaktivieren. Sie können eine schwarze Liste als letzten Ausweg verwenden, um benutzerseitige Probleme zu beheben, die andernfalls bestehen könnten. Weitere Einzelheiten finden Sie unter Deaktivieren von CFI .

Unterstützung von CFI in Blueprint-Dateien

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, treten möglicherweise einige Probleme mit Fehlern aufgrund von Funktionstyp-Konflikten und Assemblercode-Typ-Konfliktfehlern auf .

Funktionstypkonfliktfehler treten auf, weil CFI indirekte Aufrufe darauf beschränkt, nur zu Funktionen zu springen, die denselben dynamischen Typ wie der statische Typ haben, der im Aufruf verwendet wird. CFI schränkt Aufrufe von virtuellen und nicht-virtuellen Elementfunktionen so ein, dass nur zu Objekten gesprungen wird, die eine abgeleitete Klasse des statischen Typs des Objekts sind, das zum Ausführen des Aufrufs verwendet wird. Das heißt, wenn Sie Code haben, der gegen eine dieser Annahmen verstößt, wird die von CFI hinzugefügte Instrumentierung abgebrochen. Beispielsweise zeigt der Stack-Trace ein SIGABRT und logcat enthält eine Zeile über die Kontrollflussintegrität, die eine Nichtübereinstimmung gefunden hat.

Um dies 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 ist der Versuch, CFI in Code zu aktivieren, der indirekte Aufrufe der Assembly enthält. Da Assemblercode nicht typisiert wird, führt dies zu einem Typenkonflikt.

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

Wenn zu viele Assemblerfunktionen vorhanden sind und nicht alle behoben werden können, können Sie auch alle Funktionen auf die schwarze Liste setzen, die indirekte Assembleraufrufe enthalten. Dies wird nicht empfohlen, da es CFI-Prüfungen für diese Funktionen deaktiviert und dadurch Angriffsflächen öffnet.

CFI deaktivieren

Wir haben keinen Leistungs-Overhead beobachtet, daher sollten Sie CFI nicht deaktivieren müssen. Wenn es jedoch Auswirkungen auf den Benutzer gibt, können Sie CFI selektiv für einzelne Funktionen oder Quelldateien deaktivieren, indem Sie zur Kompilierzeit eine Bereinigungs-Blacklist-Datei bereitstellen. Die schwarze Liste weist den Compiler an, die CFI-Instrumentierung an bestimmten Stellen zu deaktivieren.

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

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 das Gerät nicht beeinträchtigt.