UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer (UBSan) wykonuje instrumentację w czasie kompilacji, aby sprawdzać różne typy niezdefiniowanych zachowań. UBSan może wykrywać wiele błędów związanych z niezdefiniowanym zachowaniem, ale Android obsługuje:

  • wyrównanie
  • bool
  • ograniczenia,
  • enum
  • float-cast-overflow
  • float-divide-by-zero
  • integer-divide-by-zero
  • nonnull-attribute
  • null
  • powrót
  • returns-nonnull-attribute
  • shift-base
  • shift-exponent
  • signed-integer-overflow
  • nieosiągalny
  • unsigned-integer-overflow
  • vla-bound

Przepełnienie liczby całkowitej bez znaku, choć technicznie nie jest niezdefiniowanym zachowaniem, jest uwzględniane w narzędziu do czyszczenia i używane w wielu modułach Androida, w tym w komponentach serwera multimediów, aby wyeliminować wszelkie ukryte luki w zabezpieczeniach związane z przepełnieniem liczby całkowitej.

Implementacja

W systemie kompilacji Androida możesz włączyć UBSan globalnie lub lokalnie. Aby włączyć UBSan globalnie, ustaw SANITIZE_TARGET w pliku Android.mk. Aby włączyć UBSan na poziomie modułu, ustaw LOCAL_SANITIZE i określ w pliku Android.mk niezdefiniowane zachowania, których chcesz szukać w Androidzie. Na przykład:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0

LOCAL_SRC_FILES:= sanitizer-status.c

LOCAL_MODULE:= sanitizer-status

LOCAL_SANITIZE := alignment bounds null unreachable integer
LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer

include $(BUILD_EXECUTABLE)

Odpowiednia konfiguracja planu (Android.bp):

cc_binary {

    cflags: [
        "-std=c11",
        "-Wall",
        "-Werror",
        "-O0",
    ],

    srcs: ["sanitizer-status.c"],

    name: "sanitizer-status",

    sanitize: {
        misc_undefined: [
            "alignment",
            "bounds",
            "null",
            "unreachable",
            "integer",
        ],
        diag: {
            misc_undefined: [
                "alignment",
                "bounds",
                "null",
                "unreachable",
                "integer",
            ],
        },
    },

}

Skróty UBSan

Android ma też 2 skróty: integerdefault-ub, które umożliwiają włączenie zestawu narzędzi do oczyszczania jednocześnie. Skrót integer włącza integer-divide-by-zero, signed-integer-overflowunsigned-integer-overflow. default-ub włącza sprawdzanie, które powoduje minimalne problemy z wydajnością kompilatora: bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable and vla-bound. Klasy oczyszczania liczb całkowitych można używać z opcjami SANITIZE_TARGET i LOCAL_SANITIZE, a opcji default-ub można używać tylko z opcją SANITIZE_TARGET.

Ulepszone raportowanie błędów

Domyślna implementacja UBSan w Androidzie wywołuje określoną funkcję, gdy napotka niezdefiniowane zachowanie. Domyślnie ta funkcja przerywa działanie. Jednak od października 2016 roku UBSan w Androidzie ma opcjonalną bibliotekę środowiska wykonawczego, która zapewnia bardziej szczegółowe raportowanie błędów, w tym typ napotkanego niezdefiniowanego zachowania oraz informacje o pliku i wierszu kodu źródłowego. Aby włączyć raportowanie tego błędu za pomocą sprawdzania liczb całkowitych, dodaj do pliku Android.mk ten kod:

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

Wartość LOCAL_SANITIZE włącza narzędzie do czyszczenia podczas kompilacji. LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny dla określonego narzędzia do oczyszczania. Można ustawić różne wartości dla zmiennych LOCAL_SANITIZE i LOCAL_SANITIZE_DIAG, ale włączone są tylko te kontrole, które znajdują się w zmiennej LOCAL_SANITIZE. Jeśli w przypadku zmiennej LOCAL_SANITIZE nie określono sprawdzania, ale określono je w przypadku zmiennej LOCAL_SANITIZE_DIAG, sprawdzanie nie jest włączone i nie są wyświetlane komunikaty diagnostyczne.

Oto przykład informacji dostarczanych przez bibliotekę wykonawczą UBSan:

pixel-xl:/ # sanitizer-status ubsan
sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')

Czyszczenie przepełnienia liczb całkowitych

Nieumyślne przepełnienia liczb całkowitych mogą powodować uszkodzenie pamięci lub ujawnienie informacji w zmiennych powiązanych z dostępem do pamięci lub przydzielaniem pamięci. Aby temu zapobiec, w Androidzie 7.0 zwiększyliśmy odporność platformy multimedialnej, dodając do narzędzia Clang UndefinedBehaviorSanitizer (UBSan) moduły sprawdzające przepełnienie liczb całkowitych ze znakiem i bez znaku. W Androidzie 9 rozszerzyliśmy UBSan na więcej komponentów i ulepszyliśmy obsługę tego narzędzia w systemie kompilacji.

Ma to na celu dodanie weryfikacji operacji arytmetycznych i instrukcji, które mogą spowodować przepełnienie, aby w przypadku przepełnienia bezpiecznie przerwać proces. Te narzędzia do oczyszczania mogą ograniczyć całą klasę błędów uszkodzenia pamięci i ujawnienia informacji, których przyczyną jest przepełnienie liczby całkowitej, np. pierwotna luka w zabezpieczeniach Stagefright.

Przykłady i źródło

Sanityzacja przepełnienia liczb całkowitych (IntSan) jest zapewniana przez kompilator i dodaje instrumentację do pliku binarnego w czasie kompilacji, aby wykrywać przepełnienia arytmetyczne. Jest ona domyślnie włączona w różnych komponentach platformy, np. w /platform/external/libnl/Android.bp.

Implementacja

IntSan korzysta z mechanizmów oczyszczania UBSan w przypadku przepełnienia liczb całkowitych ze znakiem i bez znaku. To ograniczenie jest włączone na poziomie poszczególnych modułów. Pomaga chronić najważniejsze komponenty Androida i nie należy jej wyłączać.

Zdecydowanie zalecamy włączenie funkcji Integer Overflow Sanitization w przypadku dodatkowych komponentów. Idealnymi kandydatami są uprzywilejowany kod natywny lub kod natywny, który analizuje niezaufane dane wejściowe użytkownika. Z narzędziem do czyszczenia wiąże się niewielki narzut na wydajność, który zależy od użycia kodu i częstotliwości operacji arytmetycznych. Spodziewaj się niewielkiego procentu kosztów dodatkowych i przeprowadź test, jeśli wydajność jest dla Ciebie ważna.

Obsługa IntSan w plikach makefile

Aby włączyć IntSan w pliku makefile, dodaj:

LOCAL_SANITIZE := integer_overflow
    # Optional features
    LOCAL_SANITIZE_DIAG := integer_overflow
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
  • LOCAL_SANITIZE przyjmuje rozdzieloną przecinkami listę narzędzi do czyszczenia, a integer_overflow to gotowy zestaw opcji dla poszczególnych narzędzi do czyszczenia przepełnienia liczb całkowitych ze znakiem i bez znaku z domyślną LISTĄ ZABLOKOWANYCH.
  • LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny w przypadku urządzeń do dezynfekcji. Trybu diagnostycznego używaj tylko podczas testowania, ponieważ nie spowoduje to przerwania działania w przypadku przepełnienia, co całkowicie niweczy zalety zabezpieczeń wynikające z tego środka. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.
  • LOCAL_SANITIZE_BLOCKLIST umożliwia określenie pliku BLOCKLIST, aby zapobiec oczyszczaniu funkcji i plików źródłowych. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Jeśli chcesz mieć większą kontrolę, włącz poszczególne narzędzia do czyszczenia, używając jednego lub obu tych flag:

LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow
    LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow

Obsługa IntSan w plikach planu

Aby włączyć w pliku projektu, np. /platform/external/libnl/Android.bp, czyszczenie przepełnienia liczb całkowitych, dodaj:

   sanitize: {
          integer_overflow: true,
          diag: {
              integer_overflow: true,
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

Podobnie jak w przypadku plików make, właściwość integer_overflow to gotowy zestaw opcji dla poszczególnych narzędzi do wykrywania przepełnienia liczb całkowitych ze znakiem i bez znaku z domyślną BLOKADĄ.

Zestaw właściwości diag włącza tryb diagnostyczny dla narzędzi do czyszczenia. Trybu diagnostycznego używaj tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co całkowicie niweluje zalety zabezpieczeń w wersjach użytkownika. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Właściwość BLOCKLIST umożliwia określenie pliku BLOCKLIST, który pozwala deweloperom zapobiegać oczyszczaniu funkcji i plików źródłowych. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Aby włączyć poszczególne narzędzia do czyszczenia, użyj tego polecenia:

   sanitize: {
          misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
          diag: {
              misc_undefined: ["signed-integer-overflow",
                               "unsigned-integer-overflow",],
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

Rozwiązywanie problemów

Jeśli w nowych komponentach włączasz oczyszczanie z przepełnienia liczb całkowitych lub korzystasz z bibliotek platformy, które mają to oczyszczanie, możesz napotkać kilka problemów z niegroźnymi przepełnieniami liczb całkowitych powodującymi przerwanie działania. Komponenty z włączonym czyszczeniem danych należy testować, aby mieć pewność, że niegroźne przepełnienia mogą zostać wykryte.

Aby znaleźć przerwania spowodowane oczyszczaniem w kompilacjach użytkowników, wyszukaj:SIGABRT awarie z komunikatami o przerwaniu wskazującymi na przepełnienie wykryte przez UBSan, np.:

pid: ###, tid: ###, name: Binder:###  >>> /system/bin/surfaceflinger <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: sub-overflow'

Ślad stosu powinien zawierać funkcję powodującą przerwanie, jednak przepełnienia występujące w funkcjach wstawianych mogą nie być widoczne w śladzie stosu.

Aby łatwiej określić główną przyczynę, włącz diagnostykę w bibliotece, która wywołuje przerwanie, i spróbuj odtworzyć błąd. Jeśli diagnostyka jest włączona, proces nie zostanie przerwany, ale będzie nadal działać. Nieprzerywanie działania pomaga zmaksymalizować liczbę niegroźnych przepełnień na określonej ścieżce wykonania bez konieczności ponownej kompilacji po naprawieniu każdego błędu. Diagnostyka generuje komunikat o błędzie, który zawiera numer wiersza i plik źródłowy powodujący przerwanie:

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:2188:32: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'size_t' (aka 'unsigned long')

Po zlokalizowaniu problematycznej operacji arytmetycznej upewnij się, że przepełnienie jest nieszkodliwe i zamierzone (np. nie ma wpływu na bezpieczeństwo). Aby rozwiązać problem z przerwaniem działania narzędzia do czyszczenia:

  • Refaktoryzacja kodu, aby uniknąć przepełnienia (przykład)
  • przepełnienie jawne za pomocą funkcji __builtin_*_overflow Clanga (przykład);
  • Wyłączanie oczyszczania w funkcji przez określenie atrybutu no_sanitize (przykład)
  • Wyłączanie oczyszczania funkcji lub pliku źródłowego za pomocą pliku BLOCKLIST (przykład)

Należy używać jak najbardziej szczegółowego rozwiązania. Na przykład duża funkcja z wieloma operacjami arytmetycznymi i jedną operacją powodującą przepełnienie powinna mieć przekształconą tylko tę jedną operację, a nie całą funkcję.

Typowe wzorce, które mogą powodować niegroźne przepełnienia, to:

  • Niejawne rzutowanie, w którym przed rzutowaniem na typ ze znakiem występuje przepełnienie bez znaku (przykład).
  • Usuwanie elementów z listy połączonej, które zmniejsza indeks pętli po usunięciu (przykład)
  • Przypisywanie do wartości -1 typu bez znaku zamiast określania rzeczywistej wartości maksymalnej (przykład)
  • pętle, które zmniejszają liczbę całkowitą bez znaku w warunku (przykład, przykład);

Przed wyłączeniem oczyszczania deweloperzy powinni upewnić się, że w przypadkach, w których narzędzie wykrywa przepełnienie, jest ono nieszkodliwe i nie powoduje niezamierzonych skutków ubocznych ani problemów z bezpieczeństwem.

Wyłączanie IntSan

IntSan możesz wyłączyć za pomocą list blokowania lub atrybutów funkcji. Wyłączaj je oszczędnie i tylko wtedy, gdy refaktoryzacja kodu jest nieuzasadniona lub gdy występuje problematyczny narzut na wydajność.

Więcej informacji o wyłączaniu IntSan za pomocą atrybutów funkcjiformatowania pliku BLOCKLIST znajdziesz w dokumentacji Clang. Lista zablokowanych powinna być ograniczona do konkretnego środka do dezynfekcji przez używanie nazw sekcji określających docelowy środek do dezynfekcji, aby uniknąć wpływu na inne środki do dezynfekcji.

Weryfikacja

Obecnie nie ma testu CTS przeznaczonego specjalnie do sprawdzania, czy funkcja Integer Overflow Sanitization działa prawidłowo. Zamiast tego upewnij się, że testy CTS przechodzą z włączoną lub wyłączoną funkcją IntSan, aby sprawdzić, czy nie ma ona wpływu na urządzenie.

Sanityzacja zakresu

BoundsSanitizer (BoundSan) dodaje do plików binarnych instrumentację, która wstawia kontrole granic wokół dostępu do tablic. Te sprawdzenia są dodawane, jeśli kompilator nie może w czasie kompilacji udowodnić, że dostęp będzie bezpieczny, a rozmiar tablicy będzie znany w czasie działania programu, aby można było go sprawdzić. Android 10 wdraża BoundSan w Bluetoothie i kodekach. BoundSan jest udostępniany przez kompilator i domyślnie włączony w różnych komponentach na platformie.

Implementacja

BoundSan korzysta z mechanizmu sprawdzania granic UBSan. Ograniczanie skutków jest włączone na poziomie poszczególnych modułów. Pomaga ona chronić najważniejsze komponenty Androida i nie należy jej wyłączać.

Zdecydowanie zalecamy włączenie BoundSan w przypadku dodatkowych komponentów. Idealnymi kandydatami są uprzywilejowany kod natywny lub złożony kod natywny, który analizuje niezaufane dane wejściowe użytkownika. Obciążenie wydajności związane z włączeniem BoundSan zależy od liczby dostępów do tablic, których nie można uznać za bezpieczne. Spodziewaj się niewielkiego średniego narzutu procentowego i sprawdź, czy wydajność jest problemem.

Włączanie BoundSan w plikach planu

BoundSan można włączyć w plikach schematu, dodając "bounds" do właściwości misc_undefined sanitize w przypadku modułów binarnych i bibliotecznych:

    sanitize: {
       misc_undefined: ["bounds"],
       diag: {
          misc_undefined: ["bounds"],
       },
       BLOCKLIST: "modulename_BLOCKLIST.txt",
diag

Właściwość diag włącza tryb diagnostyczny dla środków do dezynfekcji. Trybu diagnostycznego używaj tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co niweluje zalety zabezpieczeń i wiąże się z większym obciążeniem wydajności, dlatego nie jest zalecany w przypadku kompilacji produkcyjnych.

LISTA ZABLOKOWANYCH

Właściwość BLOCKLIST umożliwia określenie pliku BLOCKLIST, którego programiści mogą używać, aby zapobiec oczyszczaniu funkcji i plików źródłowych. Używaj tej właściwości tylko wtedy, gdy wydajność jest problemem, a docelowe pliki lub funkcje mają istotny wpływ na wydajność. Ręcznie sprawdź te pliki lub funkcje, aby upewnić się, że dostęp do tablic jest bezpieczny. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Włączanie BoundSan w plikach makefile

BoundSan można włączyć w plikach makefile, dodając "bounds" do zmiennej LOCAL_SANITIZE w przypadku modułów binarnych i bibliotecznych:

    LOCAL_SANITIZE := bounds
    # Optional features
    LOCAL_SANITIZE_DIAG := bounds
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt

LOCAL_SANITIZE akceptuje listę funkcji czyszczących rozdzielonych przecinkami.

LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny. Trybu diagnostycznego używaj tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co niweluje zalety zabezpieczeń i wiąże się z większym obciążeniem wydajności, dlatego nie jest zalecany w przypadku kompilacji produkcyjnych.

LOCAL_SANITIZE_BLOCKLIST umożliwia określenie pliku BLOCKLIST, który pozwala deweloperom zapobiegać oczyszczaniu funkcji i plików źródłowych. Używaj tej właściwości tylko wtedy, gdy wydajność jest problemem, a docelowe pliki lub funkcje mają istotny wpływ na wydajność. Ręcznie sprawdź te pliki lub funkcje, aby upewnić się, że dostęp do tablic jest bezpieczny. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Wyłączanie BoundSan

Możesz wyłączyć BoundSan w funkcjach i plikach źródłowych za pomocą list blokowanych lub atrybutów funkcji. Najlepiej pozostawić włączoną funkcję BoundSan. Wyłącz ją tylko wtedy, gdy funkcja lub plik powoduje duże obciążenie i źródło zostało sprawdzone ręcznie.

Więcej informacji o wyłączaniu BoundSan za pomocą atrybutów funkcjiformatowania pliku BLOCKLIST znajdziesz w dokumentacji Clang LLVM. Ogranicz zakres listy zablokowanych do konkretnego środka dezynfekującego, używając nazw sekcji określających docelowy środek dezynfekujący, aby uniknąć wpływu na inne środki dezynfekujące.

Weryfikacja

Nie ma testu CTS przeznaczonego specjalnie dla BoundSan. Zamiast tego upewnij się, że testy CTS przebiegają pomyślnie z włączoną lub wyłączoną funkcją BoundSan, aby sprawdzić, czy nie ma ona wpływu na urządzenie.

Rozwiązywanie problemów

Po włączeniu BoundSan dokładnie przetestuj komponenty, aby upewnić się, że wszystkie wcześniej niewykryte dostępy poza zakresem zostały rozwiązane.

Błędy BoundSan można łatwo rozpoznać, ponieważ zawierają one ten komunikat o przerwaniu:

    pid: ###, tid: ###, name: Binder:###  >>> /system/bin/foobar <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: out-of-bounds'

W trybie diagnostycznym do pliku logcat zapisywane są plik źródłowy, numer wiersza i wartość indeksu. Domyślnie ten tryb nie wyświetla komunikatu o przerwaniu. Sprawdź logcat, aby wykryć ewentualne błędy.

    external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'