Sekcje kodu wykonywalnego w binarnych plikach systemowych AArch64 są domyślnie oznaczone jako tylko do wykonania (nieczytelne) w celu wzmocnienia zabezpieczeń przed atakami polegającymi na ponownym wykorzystaniu kodu w ramach procesu JIT. Kod, który łączy dane i kod, oraz kod, który celowo sprawdza te sekcje (bez wcześniejszego ponownego mapowania segmentów pamięci jako możliwych do odczytu) nie będzie już działać. Aplikacje z docelowym pakietem SDK 10 (poziom interfejsu API 29 lub wyższy) są dotknięte, jeśli próbują odczytać sekcje kodu w bibliotekach systemowych z obsługą pamięci tylko do odczytu (XOM) w pamięci bez wcześniejszego oznaczenia ich jako do odczytu.
Aby w pełni korzystać z tej metody zapobiegania, wymagana jest obsługa zarówno sprzętowa, jak i jądrowa. Bez tej pomocy ograniczenie może być tylko częściowo skuteczne. Wersja 4.9 jądra wspólnego Androida zawiera odpowiednie poprawki, które zapewniają pełną obsługę na urządzeniach ARMv8.2.
Implementacja
Binarny plik AArch64 wygenerowany przez kompilator zakłada, że kod i dane nie są zmieszane. Włączenie tej funkcji nie wpływa negatywnie na wydajność urządzenia.
W przypadku kodu, który musi celowo analizować pamięć w segmentach kodu wykonywalnego, zaleca się wywołanie funkcji mprotect
w segmentach kodu wymagających sprawdzenia, aby umożliwić ich odczyt, a potem usunięcie możliwości odczytu po zakończeniu sprawdzania.
Ta implementacja powoduje, że odczyty z segmentów pamięci oznaczonych jako tylko do wykonywania powodują błąd segmentacji (SEGFAULT
). Może to być spowodowane błędem, luką w zabezpieczeniach, danymi zmieszanymi z kodem (literal pooling) lub celową kontrolą pamięci.
Obsługa urządzeń i wpływ
Urządzenia z uprzednim sprzętem lub wcześniejszymi wersjami jądra (starszymi niż 4.9) bez wymaganych poprawek mogą nie w pełni obsługiwać tej funkcji lub nie korzystać z niej. Urządzenia bez obsługi jądra mogą nie wymuszać dostępu użytkownika do pamięci tylko do odczytu, jednak kod jądra, który wyraźnie sprawdza, czy strona jest czytelna, może nadal wymuszać tę właściwość, taką jak process_vm_readv()
.
Aby mieć pewność, że jądro będzie respektować strony userland oznaczone jako tylko do wykonania, musisz ustawić w jądrze flagę CONFIG_ARM64_UAO
. Starsze urządzenia ARMv8 lub urządzenia ARMv8.2 z wyłączoną opcją zastąpienia dostępu użytkownika (UAO) mogą nie korzystać z tego rozwiązania i nadal mogą odczytywać strony tylko do wykonania za pomocą syscalli.
Refaktoryzacja istniejącego kodu
Kod przeniesiony z AArch32 może zawierać pomieszane dane i kod, co może powodować problemy. W wielu przypadkach rozwiązanie tych problemów polega na przeniesieniu stałych do sekcji .data
w pliku assembly.
Złożony ręcznie kod może wymagać przeformułowania, aby oddzielić stałe współdzielone lokalnie.
Przykłady:
Binarny plik wygenerowany przez kompilator Clang nie powinien mieć problemów z mieszaniem danych w kodzie. Jeśli kod wygenerowany przez kompilator GNU (GCC) jest uwzględniony (z biblioteki statycznej), sprawdź wyjściowy plik binarny, aby upewnić się, że stałe nie zostały zgrupowane w sekcje kodu.
Jeśli w przypadku sekcji kodu wykonywalnego konieczna jest analiza kodu, najpierw wywołaj funkcję mprotect
, aby oznaczyć kod jako czytelny. Następnie po zakończeniu operacji ponownie wywołaj mprotect
, aby oznaczyć plik jako nieczytelny.
Włączanie XOM
Domyślnie w systemie kompilacji dla wszystkich 64-bitowych plików binarnych włączona jest opcja tylko do wykonywania.
Wyłączanie XOM
Możesz wyłączyć tylko do wykonania na poziomie modułu, całego drzewa podkatalogów lub globalnie dla całej kompilacji.
XOM można wyłączyć w przypadku poszczególnych modułów, których nie można przerobić, lub które wymagają odczytu kodu wykonywalnego. W tym celu należy ustawić zmienne LOCAL_XOM
i xom
na false
.
// Android.mk LOCAL_XOM := false // Android.bp cc_binary { // or other module types ... xom: false, }
Jeśli pamięć tylko do wykonywania jest wyłączona w bibliotece statycznej, system kompilacji zastosuje to do wszystkich zależnych modułów tej biblioteki statycznej. Możesz to zmienić, używając funkcji xom: true,
.
Aby wyłączyć pamięć tylko do odczytu w określonym podkatalogu (np. foo/bar/), prześlij wartość do XOM_EXCLUDE_PATHS
.
make -j XOM_EXCLUDE_PATHS=foo/bar
Możesz też ustawić zmienną PRODUCT_XOM_EXCLUDE_PATHS
w konfiguracji produktu.
Możesz globalnie wyłączyć binarne pliki wykonywalne, przekazując parametr ENABLE_XOM=false
do polecenia make
.
make -j ENABLE_XOM=false
Weryfikacja
W przypadku pamięci tylko do odczytu nie ma dostępnych testów CTS ani weryfikacji. Możesz ręcznie zweryfikować pliki binarne za pomocą narzędzia readelf
i sprawdzenia flag segmentu.