Google przepisało oprogramowanie układowe pvmfm systemu Android w języku Rust

W ramach prac nad wzmocnieniem bezpieczeństwa krytycznych komponentów oprogramowania platformy Android firma Google przepisała oprogramowanie układowe pvmfm w języku Rust, które służy do organizowania działania maszyn wirtualnych uruchamianych przez hiperwizor pVM z Android Virtualization Framework. Wcześniej oprogramowanie układowe zostało napisane w języku C i zaimplementowane na bootloaderze U-Boot, w którego kodzie wcześniej odkryto luki spowodowane problemami z obsługą pamięci.

Oprogramowanie układowe przepisane w Rust jest zawarte w Androidzie 14, a uniwersalne biblioteki utworzone podczas opracowywania oprogramowania układowego są projektowane jako pakiety crate i przenoszone do społeczności Rust. Na przykład pakiet smccc został opublikowany w celu obsługi interfejsów ARM PSCI (Power State Coordination Interface) i wywołań SMCCC (SMC Calling Convention), a pakiet aarch64-paging do manipulowania tabelami stron pamięci. Wykonano również prace w celu wyeliminowania błędów i rozszerzenia funkcjonalności istniejącego pakietu virtio-drivers o implementację sterowników VirtIO. Oprócz platformy Android pakiety te są zaangażowane w projekt Oak, który rozwija komponenty do przesyłania, przechowywania i przetwarzania danych w chronionych środowiskach (TEE, Trusted Execution Environment).

Hiperwizor pVM przejmuje kontrolę już na wczesnym etapie procesu rozruchu i zapewnia pełną izolację pamięci. maszyna wirtualna i środowisko hosta, uniemożliwiając systemowi hosta dostęp do chronionych maszyn wirtualnych, na których przetwarzane są poufne dane. Oprogramowanie układowe pvmfm (Protected Virtual Machine Firmware) przejmuje kontrolę natychmiast po uruchomieniu maszyny wirtualnej, weryfikuje utworzone środowisko i decyduje, czy przerwać rozruch w przypadku wykrycia problemów z integralnością, lub generuje certyfikat rozruchowy dla systemu gościa, jeśli łańcuch zaufania zostanie potwierdzony.

Refaktoryzacja w Rust ułatwia i czyni bezpieczniejszym przestrzeganie „zasady dwóch” stosowanej przez Google w celu utrzymania bezpieczeństwa komponentów systemu Android. Zgodnie z tą zasadą, każdy dodany kod musi spełniać nie więcej niż dwa z trzech warunków: pracować z niezaufanymi danymi wejściowymi, używać niebezpiecznego języka programowania (C/C++) i działać z podwyższonymi uprawnieniami. Z tej zasady wynika, że ​​kod do przetwarzania danych zewnętrznych musi być albo ograniczony do minimalnych uprawnień (izolowany), albo napisany w bezpiecznym języku programowania. Według statystyk Google, około 70% wszystkich niebezpiecznych luk w zabezpieczeniach zidentyfikowanych w systemie Android jest spowodowanych błędami w obsłudze pamięci.

Rust koncentruje się na bezpieczeństwie pamięci i zmniejsza ryzyko luk spowodowanych przez problemy takie jak użycie po zwolnieniu i przepełnienia bufora. Rust zapewnia bezpieczeństwo pamięci w czasie kompilacji poprzez sprawdzanie referencji, śledzenie własności obiektów i rozliczanie czasu życia obiektu (zakresu), a także poprzez walidację dostępu do pamięci w czasie wykonywania. Rust zapewnia również ochronę przed przepełnieniami całkowitymi, wymaga inicjalizacji zmiennych przed użyciem, lepiej obsługuje błędy w bibliotece standardowej, domyślnie wykorzystuje koncepcję niezmiennych referencji i zmiennych oraz oferuje silne typowanie statyczne w celu zminimalizowania błędów logicznych.

Jedną z trudności napotykanych podczas opracowywania komponentów niskiego poziomu, takich jak sterowniki w Rust, jest konieczność pracy z gołymi wskaźnikami w trybie niebezpiecznym, ponieważ Rust został zaprojektowany z myślą o wykorzystaniu pamięci przydzielonej w programie, a w kodzie działającym bez warstw na sprzęcie konieczny jest dostęp do pamięci współdzielonej i MMIO. Obecnie możliwości Rust w zakresie pracy z gołymi wskaźnikami pozostawiają wiele do życzenia, ale powinno się to zmienić, gdy ustabilizuje się obsługa makr offset_of, slice_ptr_get i slice_ptr_len.

Wśród niedociągnięć warto również odnotować potrzebę ulepszonej składni dostępu do pól struktury i indeksów tablic poprzez gołe wskaźniki bez tworzenia odniesień, a także ograniczenia w tworzeniu bezpiecznych powiązań w przypadku niebezpiecznych operacji, które mogą powodować niezdefiniowane zachowanie i nie mogą być sprawdzane przez kompilator. Na przykład takich powiązań nie można tworzyć dla operacji z tabelami stron pamięci, ponieważ mapowanie stron w jednej części programu może wpływać na inne części.

Jeśli chodzi o rozmiar powstałego kodu, stara wersja oprogramowania układowego pVM zajmowała 220 kB, a nowa — 460 kB, ale jednocześnie do przepisanej wersji dodano nowe funkcje, dzięki którym udało się pozbyć niektórych innych komponentów używanych podczas ładowania. W rezultacie całkowity rozmiar wszystkich starych i nowych komponentów ładujących okazał się porównywalny. Należy zauważyć, że gdy rozmiar jest ważniejszy od wydajności, wyniki porównywalne z językiem C można osiągnąć, włączając dodatkowe tryby optymalizacji rozmiaru w kompilatorze, odrzucając zbędne zależności i nie używając narzędzi formatujących ciągi znaków.

Ponadto trwają prace nad wdrożeniem możliwości uruchamiania Trusted Applications napisanych w Rust w systemie operacyjnym Trusty, który zapewnia TEE (Trusted Execution Environment) dla Androida, który działa równolegle z Androidem na tym samym procesorze w oddzielnym, odizolowanym środowisku. Trusty jest używany w urządzeniach Pixel i już używa Rust w bibliotekach i komponentach systemowych (jądro pozostaje w C).

Źródło: opennet.ru

Dodaj komentarz