UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer (UBSan) wykonuje instrumentację w czasie kompilacji, aby sprawdzać różne typy niezdefiniowanego zachowania. UBSan może wykrywać wiele błędów nieokreślonego zachowania, a Android obsługuje:

  • wyrównanie
  • bool
  • bounds
  • 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

Niezdefiniowane zachowanie unsigned-integer-overflow jest uwzględnione w sanityzatorze i używane w wielu modułach Androida, w tym w komponentach mediaserver, aby wyeliminować ukryte luki w zabezpieczeniach związane z przepełnieniem liczby całkowitej bez znaku.

Implementacja

W systemie kompilacji Androida możesz włączyć UBSan globalnie lub lokalnie. Aby włączyć UBSan globalnie, ustaw SANITIZE_TARGET w Android.mk. Aby włączyć UBSan na poziomie poszczególnych modułów, ustaw LOCAL_SANITIZE i określ nieokreślone zachowania, których chcesz szukać w Android.mk. 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)

I odpowiednia konfiguracja szablonu (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ą jednoczesne włączanie zestawu środków do dezynfekcji. Wartość całkowita włącza integer-divide-by-zero, signed-integer-overflowunsigned-integer-overflow. default-ub włącza mechanizmy kontroli, które mają minimalny wpływ na wydajność kompilatora: bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable and vla-bound. Klasy sanitizerów liczb całkowitych można używać z SANITIZE_TARGET i LOCAL_SANITIZE, natomiast domyślnego ub można używać tylko z SANITIZE_TARGET.

Lepsze raportowanie błędów

Domyślna implementacja UBSan w Androidzie wywołuje określoną funkcję, gdy wystąpi nieokreślone działanie. Domyślnie ta funkcja jest przerwana. Od października 2016 r. UBSan na Androida ma jednak opcjonalną bibliotekę czasu wykonywania, która zapewnia bardziej szczegółowe raportowanie błędów, w tym informacje o występującym nieokreślonym zachowaniu oraz o pliku i wierszu kodu źródłowego. Aby włączyć raportowanie błędów z kontrolą liczb całkowitych, dodaj do pliku Android.mk ten fragment kodu:

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

Wartość LOCAL_SANITIZE włącza funkcję sterylizacji podczas kompilacji. LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny dla określonego środka dezynfekcyjnego. Można ustawić zmienne LOCAL_SANITIZE i LOCAL_SANITIZE_DIAG na różne wartości, ale tylko te kontrole w LOCAL_SANITIZE są włączone. Jeśli kontrola nie jest określona w LOCAL_SANITIZE, ale jest określona w LOCAL_SANITIZE_DIAG, nie jest włączona i nie są wyświetlane komunikaty diagnostyczne.

Oto przykład informacji udostępnianych przez bibliotekę 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')

Sanityzacja przepełnienia liczb całkowitych

Niezamierzone przepełnienia liczb całkowitych mogą powodować uszkodzenie pamięci lub podatność na luki w zabezpieczeniach w przypadku zmiennych związanych z dostępem do pamięci lub alokacją pamięci. Aby temu zapobiec, w Androidzie 7.0 dodaliśmy do Clanga UndefinedBehaviorSanitizer (UBSan) do czyszczenia danych o przekroczeniu wartości natężenia i nieoznaczonych liczb całkowitych, aby wzmocnić mechanizm obsługi multimediów. W Androidzie 9 rozszerzyliśmy UBSan, aby obejmował więcej komponentów, i ulepsiliśmy obsługę tego systemu kompilacji.

Ma on na celu dodanie weryfikacji operacji arytmetycznych i instrukcji, które mogą doprowadzić do przepełnienia, aby bezpiecznie przerwać proces, jeśli do niego dojdzie. Te funkcje mogą łagodzić całą klasę podatności na naruszenie integralności pamięci i ujawnienie informacji, w których przypadku przyczyną jest przepełnienie typu integer, np. pierwotna podatność 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, na przykład w /platform/external/libnl/Android.bp.

Implementacja

IntSan używa oczyszczaczy przepełnienia podpisanych i niepodpisanych liczb całkowitych z UBSan. Ta zapora jest włączona na poziomie modułu. Pomaga chronić najważniejsze komponenty Androida i nie należy go wyłączać.

Zdecydowanie zalecamy włączenie funkcji Sanitization Overflow Integer w przypadku dodatkowych komponentów. Najlepszym rozwiązaniem jest zaszyty w aplikacji kod natywny lub kod natywny, który analizuje dane wejściowe od nieuczciwego użytkownika. Sanitizer powoduje niewielki narzut na wydajność, który zależy od sposobu użycia kodu i częstotliwości operacji arytmetycznych. Należy się spodziewać niewielkiego procentu na potrzeby obsługi, a w razie potrzeby przetestować skuteczność.

Obsługa IntSan w makefiles

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 listę środków zaradczych rozdzielonych przecinkami, przy czym integer_overflow to wstępnie skompilowany zestaw opcji dla poszczególnych środków zaradczych do błędów przepełnienia liczb całkowitych ze znakiem i bez znaku z domyślną listą blokowania.
  • LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny dezynfekcji. Tryb diagnostyczny należy stosować tylko podczas testowania, ponieważ nie powoduje on przerwania w przypadku przepełnienia, co całkowicie niweczy zalety zabezpieczeń w ramach środków zaradczych. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.
  • LOCAL_SANITIZE_BLOCKLIST umożliwia określenie pliku BLOCKLIST, aby zapobiec czyszczeniu 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 sanitizery osobno, używając jednego lub obu flag:

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

Obsługa IntSan w plikach blueprint

Aby włączyć oczyszczanie przepełnienia liczb całkowitych w pliku projektu, takim jak /platform/external/libnl/Android.bp, 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 wstępnie skompilowany zestaw opcji dla poszczególnych oczyszczaczy przepełnienia liczb całkowitych ze znakiem i bez znaku z domyślną listą blokowania.

Zestaw właściwości diag umożliwia tryb diagnostyczny dla środków odkażających. Tryb diagnostyczny należy stosować tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co całkowicie niweczy korzyści z punktu widzenia bezpieczeństwa w postaci ograniczeń w kompilacji użytkownika. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

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

Aby włączyć środki do dezynfekcji pojedynczo, użyj:

   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łączysz czyszczanie danych w przypadku przepełnienia liczb całkowitych w nowych komponentach lub korzystasz z bibliotek platformy, które mają włączone czyszczenie danych w przypadku przepełnienia liczb całkowitych, możesz napotkać kilka problemów z nieszkodliwym przepełnieniem liczb całkowitych, które powoduje przerwanie. Należy przetestować komponenty z włączoną funkcją sterylizacji, aby mieć pewność, że nieszkodliwe przepełnienia zostaną wykryte.

Aby znaleźć przerwania spowodowane czyszczeniem w kompilacji użytkownika, wyszukaj SIGABRT awarie z wiadomościami o przerwaniu, które wskazują na przepełnienie wychwycone przez UBSan, takie jak:

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ę, która spowodowała przerwanie, ale przepełnienia występujące w funkcjach wbudowanych mogą nie być widoczne w śladzie stosu.

Aby łatwiej ustalić 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, tylko będzie kontynuowany. Nieprzerywanie działania pomaga zmaksymalizować liczbę nieszkodliwych przepełnień w konkretnej ścieżce wykonania bez konieczności ponownego kompilowania po naprawieniu każdego błędu. Diagnostyka wygeneruje komunikat o błędzie, który zawiera numer wiersza i plik źródłowy, które spowodowały 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 znalezieniu problemowej operacji arytmetycznej sprawdź, czy przepełnienie jest nieszkodliwe i zamierzone (np. nie ma wpływu na bezpieczeństwo). Aby rozwiązać problem z przerwą w działaniu programu do sterylizacji:

  • Refaktoryzacja kodu w celu uniknięcia przepełnienia (przykład)
  • Przepełnienie w prosty sposób za pomocą funkcji __builtin_*_overflow w Clang (przykład)
  • Wyłączenie funkcji oczyszczania przez określenie atrybutu no_sanitize (przykład)
  • Wyłączenie oczyszczania funkcji lub pliku źródłowego za pomocą pliku BLOCKLIST (przykład)

Należy użyć jak najbardziej szczegółowego rozwiązania. Na przykład duża funkcja z wiele operacjami arytmetycznymi i jedną operacją z przepełnieniem powinna zostać przebudowana w jedną operację, a nie całą funkcję, która zostanie umieszczona na liście zablokowanych.

Do łagodnego przepełnienia może dojść w przypadku:

  • Implikowane konwersje, w których przypadku przed konwersją na typ z znakami występuje przepełnienie bez znaku (przykład).
  • usuwanie połączonych list, które zmniejsza indeks pętli podczas usuwania (przykład);
  • Przypisanie typu bez znaku do wartości -1 zamiast podania rzeczywistej wartości maksymalnej (przykład)
  • Pętle, które w warunku zmniejszają bezwzględną liczbę całkowitą (przykład, przykład).

Zalecamy, aby deweloperzy przed wyłączeniem skanowania sprawdzali, czy przypadki wykrycia przez skanowanie przepełnienia są rzeczywiście nieszkodliwe i nie mają nieprzewidzianych skutków ubocznych ani nie wpływają na bezpieczeństwo.

Wyłączanie IntSan

Możesz wyłączyć IntSan za pomocą list zablokowanych adresów lub atrybutów funkcji. Wyłączaj je oszczędnie i tylko wtedy, gdy refaktoryzacja kodu jest nieuzasadniona lub gdy występuje problem z wydajnością.

Więcej informacji o wyłączaniu funkcji IntSan znajdziesz w dokumentacji upstream Clang dotyczącej atrybutów funkcji i formatowania pliku BLOCKLIST. Lista zablokowanych powinna być ograniczona do konkretnego środka dezynfekującego, co można osiągnąć, używając nazw sekcji określających docelowy środek dezynfekujący, aby uniknąć wpływu na inne środki dezynfekujące.

Weryfikacja

Obecnie nie ma testu CTS przeznaczonego specjalnie do sprawdzania funkcji Integer Overflow Sanitization. Zamiast tego sprawdź, czy testy CTS są przeprowadzane z włączonym lub wyłączonym IntSan, aby upewnić się, że nie mają one wpływu na urządzenie.

Sanityzacja ograniczeń

BoundsSanitizer (BoundSan) dodaje do binarek mechanizmy, aby wstawiać mechanizmy kontroli zakresu wokół dostępów do tablic. Te kontrole są dodawane, jeśli kompilator nie może udowodnić w czasie kompilacji, że dostęp będzie bezpieczny, oraz jeśli rozmiar tablicy będzie znany w czasie wykonywania, aby można było go sprawdzić. Android 10 wdraża BoundSan w Bluetooth i kodeki. BoundSan jest dostarczany przez kompilator i jest domyślnie włączony w różnych komponentach na całej platformie.

Implementacja

BoundSan używa środka do dezynfekcji UBSan. Ta metoda zapobiegania jest włączona na poziomie modułu. Pomaga chronić najważniejsze komponenty Androida i nie należy go wyłączać.

Zdecydowanie zalecamy włączenie BoundSan dla dodatkowych komponentów. Najlepszym rozwiązaniem jest zaimplementowanie uprzywilejowanego kodu natywnego lub złożonego kodu natywnego, który analizuje dane wejściowe nieuczciwego użytkownika. Narzut na wydajność związany z włączeniem BoundSank zależy od liczby dostępów do tablicy, które nie mogą być uznane za bezpieczne. Należy się spodziewać niewielkiego średniego procentowego obciążenia i sprawdzać, czy skuteczność jest zadowalająca.

Włączanie BoundSan w plikach blueprint

Funkcję BoundSan można włączyć w plikach blueprintów, dodając wartość "bounds" do właściwości misc_undefined w przypadku modułów binarnych i bibliotek:

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

Właściwość diag umożliwia tryb diagnostyczny dla urządzeń do dezynfekcji. Tryb diagnostyczny należy stosować tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co niweczy zalety zapobiegania w zakresie bezpieczeństwa i powoduje większy nakład na wydajność, dlatego nie jest zalecany w przypadku wersji produkcyjnych.

BLOCKLIST

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

Włączanie BoundSan w makefile

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

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

LOCAL_SANITIZE może zawierać listę środków dezynfekujących rozdzielonych przecinkami.

LOCAL_SANITIZE_DIAG włącza tryb diagnostyczny. Tryb diagnostyczny należy stosować tylko podczas testowania. Tryb diagnostyczny nie przerywa działania w przypadku przepełnienia, co niweczy zalety zabezpieczeń w ramach zapobiegania i powoduje większy narzut na wydajność, dlatego nie jest zalecany w przypadku wersji produkcyjnych.

LOCAL_SANITIZE_BLOCKLIST umożliwia określenie pliku blokady, który pozwala deweloperom zapobiegać czyszczeniu funkcji i plików źródłowych. Używaj tej właściwości tylko wtedy, gdy masz problemy ze skutecznością, a docelowe pliki lub funkcje mają na nią znaczący wpływ. Ręcznie sprawdź te pliki lub funkcje, aby upewnić się, że dostęp do tablicy 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 zablokowanych lub atrybutów funkcji. Najlepiej jest pozostawić włączoną funkcję BoundSan, więc wyłącz ją tylko wtedy, gdy funkcja lub plik powoduje znaczne spowolnienie działania i jego źródło zostało sprawdzone ręcznie.

Więcej informacji o wyłączaniu BoundSan za pomocą atrybutów funkcji i formatowania pliku BLOCKLIST znajdziesz w dokumentacji Clang LLVM. Ogranicz listę zablokowanych do konkretnego środka dezynfekującego, używając nazw sekcji określających ten środek, aby nie wpływać na inne środki dezynfekujące.

Weryfikacja

Nie ma testów CTS przeznaczonych specjalnie dla BoundSan. Zamiast tego sprawdź, czy testy CTS są przeprowadzane prawidłowo z włączonym lub wyłączonym BoundSan, aby upewnić się, że nie wpływają one na urządzenie.

Rozwiązywanie problemów

Po włączeniu BoundSan dokładnie przetestuj komponenty, aby upewnić się, że wszystkie wcześniej niewykrywalne próby dostępu poza granice zostały rozwiązane.

Błędy BoundSan można łatwo zidentyfikować, ponieważ zawierają ten komunikat o zakończeniu:

    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 plik źródłowy, numer wiersza i wartość indeksu są drukowane w pliku logcat. Domyślnie ten tryb nie powoduje wyświetlenia komunikatu o aborcji. Sprawdź logcat, aby sprawdzić, czy nie ma błędów.

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