Integralność przepływu sterowania

Od 2016 r. około 86% wszystkich luk w zabezpieczeniach Androida jest związanych z bezpieczeństwem pamięci. Większość luk jest wykorzystywana przez osoby atakujące, zmieniając normalny przepływ kontroli aplikacji w celu wykonywania dowolnych złośliwych działań ze wszystkimi uprawnieniami wykorzystywanej aplikacji. Integralność przepływu sterowania (CFI) to mechanizm bezpieczeństwa, który uniemożliwia zmiany w oryginalnym wykresie przepływu sterowania skompilowanego pliku binarnego, co znacznie utrudnia przeprowadzenie takich ataków.

W systemie Android 8.1 umożliwiliśmy implementację CFI w stosie multimediów przez LLVM. W Androidzie 9 włączyliśmy CFI w większej liczbie komponentów, a także w jądrze. System CFI jest domyślnie włączony, ale musisz włączyć CFI jądra.

CFI LLVM wymaga kompilacji z optymalizacją czasu łącza (LTO) . LTO zachowuje reprezentację plików obiektowych w kodzie bitowym LLVM aż do czasu połączenia, co pozwala kompilatorowi lepiej przemyśleć, jakie optymalizacje można przeprowadzić. Włączenie LTO zmniejsza rozmiar końcowego pliku binarnego i poprawia wydajność, ale wydłuża czas kompilacji. Podczas testowania na Androidzie połączenie LTO i CFI powoduje znikomy narzut związany z rozmiarem kodu i wydajnością; w kilku przypadkach obie uległy poprawie.

Więcej szczegółów technicznych na temat CFI i sposobu obsługi innych kontroli wyprzedzających można znaleźć w dokumentacji projektowej LLVM .

Przykłady i źródło

CFI jest dostarczane przez kompilator i dodaje oprzyrządowanie do pliku binarnego podczas kompilacji. Wspieramy CFI w zestawie narzędzi Clang i systemie kompilacji Androida w AOSP.

CFI jest domyślnie włączone dla urządzeń Arm64 dla zestawu komponentów w /platform/build/target/product/cfi-common.mk . Jest także włączana bezpośrednio w zestawie plików makefile/blueprint komponentów multimedialnych, takich jak /platform/frameworks/av/media/libmedia/Android.bp i /platform/frameworks/av/cmds/stagefright/Android.mk .

Wdrożenie systemu CFI

CFI jest domyślnie włączone, jeśli używasz Clang i systemu kompilacji Androida. Ponieważ CFI pomaga chronić użytkowników Androida, nie należy go wyłączać.

W rzeczywistości gorąco zachęcamy do włączenia CFI dla dodatkowych komponentów. Idealnymi kandydatami są uprzywilejowany kod natywny lub kod natywny, który przetwarza dane wejściowe niezaufanego użytkownika. Jeśli używasz clang i systemu kompilacji Androida, możesz włączyć CFI w nowych komponentach, dodając kilka linii do plików makefile lub plików planów.

Wspieranie CFI w plikach makefile

Aby włączyć CFI w pliku make, takim jak /platform/frameworks/av/cmds/stagefright/Android.mk , dodaj:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE określa CFI jako środek dezynfekujący podczas kompilacji.
  • LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny dla CFI. Tryb diagnostyczny drukuje dodatkowe informacje debugowania w logcat podczas awarii, co jest przydatne podczas opracowywania i testowania kompilacji. Pamiętaj jednak, aby usunąć tryb diagnostyczny w kompilacjach produkcyjnych.
  • LOCAL_SANITIZE_BLACKLIST umożliwia komponentom selektywne wyłączanie instrumentacji CFI dla poszczególnych funkcji lub plików źródłowych. Możesz użyć czarnej listy w ostateczności, aby rozwiązać wszelkie problemy, na które napotykają użytkownicy, które w przeciwnym razie mogłyby wystąpić. Aby uzyskać więcej informacji, zobacz Wyłączanie CFI .

Wspieranie CFI w plikach planów

Aby włączyć CFI w pliku planu, takim jak /platform/frameworks/av/media/libmedia/Android.bp , dodaj:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

Rozwiązywanie problemów

Jeśli włączasz CFI w nowych komponentach, możesz napotkać kilka problemów związanych z błędami niedopasowania typu funkcji i błędami niedopasowania typu kodu zespołu .

Błędy niezgodności typu funkcji występują, ponieważ CFI ogranicza wywołania pośrednie do przeskakiwania tylko do funkcji, które mają ten sam typ dynamiczny, co typ statyczny użyty w wywołaniu. CFI ogranicza wirtualne i niewirtualne wywołania funkcji członkowskich do przeskakiwania tylko do obiektów, które są klasą pochodną typu statycznego obiektu użytego do wykonania wywołania. Oznacza to, że jeśli masz kod, który narusza którekolwiek z tych założeń, instrumentacja dodana przez CFI zostanie przerwana. Na przykład ślad stosu pokazuje SIGABRT, a logcat zawiera wiersz informujący o znalezieniu niezgodności integralności przepływu sterowania.

Aby to naprawić, upewnij się, że wywoływana funkcja ma ten sam typ, który został zadeklarowany statycznie. Oto dwa przykładowe CL:

Innym możliwym problemem jest próba włączenia CFI w kodzie zawierającym pośrednie wywołania asemblera. Ponieważ kod zestawu nie jest wpisany, powoduje to niezgodność typu.

Aby to naprawić, utwórz opakowania kodu natywnego dla każdego wywołania zestawu i nadaj opakowaniom tę samą sygnaturę funkcji, co wskaźnik wywołujący. Opakowanie może następnie bezpośrednio wywołać kod zestawu. Ponieważ oddziały bezpośrednie nie są obsługiwane przez CFI (nie można ich przeznaczyć w czasie wykonywania, a zatem nie stanowią zagrożenia bezpieczeństwa), rozwiąże to problem.

Jeśli jest zbyt wiele funkcji asemblera i nie można ich wszystkich naprawić, możesz także umieścić na czarnej liście wszystkie funkcje, które zawierają pośrednie wywołania asemblera. Nie jest to zalecane, ponieważ uniemożliwia kontrolę CFI tych funkcji, otwierając w ten sposób powierzchnię ataku.

Wyłączenie CFI

Nie zaobserwowaliśmy żadnego narzutu na wydajność, więc nie powinno być potrzeby wyłączania CFI. Jeśli jednak wystąpi wpływ na użytkownika, można selektywnie wyłączyć CFI dla poszczególnych funkcji lub plików źródłowych, dostarczając plik czarnej listy narzędzia oczyszczającego w czasie kompilacji. Czarna lista instruuje kompilator, aby wyłączyć instrumentację CFI w określonych lokalizacjach.

System kompilacji Androida zapewnia obsługę czarnych list poszczególnych komponentów (umożliwiając wybór plików źródłowych lub poszczególnych funkcji, które nie otrzymają instrumentacji CFI) zarówno dla Make, jak i Soong. Więcej szczegółów na temat formatu pliku czarnej listy można znaleźć w dokumentacji Clang .

Walidacja

Obecnie nie ma testu CTS specjalnie dla CFI. Zamiast tego upewnij się, że testy CTS przeszły pomyślnie z włączoną funkcją CFI lub bez niej, aby sprawdzić, czy CFI nie ma wpływu na urządzenie.