Google переписал на языке Rust прошивку pvmfm, используемую в Android

В рамках работы по усилению безопасности критически важных программных компонентов платформы Android компания Google переписала на языке Rust прошивку pvmfm, используемую для организации работы виртуальных машин, запускаемых гипервизором pVM из состава Android Virtualization Framework. Ранее прошивка была написана на языке Си и реализована поверх загрузчика U-Boot, в коде которого ранее находили уязвимости, вызванные проблемами при работе с памятью.

Переписанная на Rust прошивка включена в состав Android 14, а созданные в процессе разработки прошивки универсальные библиотеки, оформлены в виде crate-пакетов и переданы сообществу Rust. Например, опубликован пакет smccc для поддержки ARM-интерфейсов PSCI (Power State Coordination Interface) и вызовов SMCCC (SMC Calling Convention), и пакет aarch64-paging для манипуляции таблицами страниц памяти. Также проведена работа по устранению ошибок и расширению функциональности существующего пакета virtio-drivers с реализацией драйверов VirtIO. Помимо платформы Android указанные пакеты задействованы в проекте Oak, развивающем компоненты для передачи, хранения и обработки данных в защищённых окружениях (TEE, Trusted Execution Environment).

Гипервизор pVM получает управление на ранней стадии загрузки и обеспечивает полную изоляцию памяти виртуальных машин и хост-окружения, не позволяя из хост-системы получить доступ к защищённым виртуальным машинам, в которых выполняется обработка конфиденциальных данных. Прошивка pvmfm (Protected Virtual Machine Firmware) получает управление сразу после запуска виртуальной машины, выполняет проверку сформированного окружения и принимает решение об аварийном прерывании загрузки в случае выявления проблем с целостностью или генерирует загрузочный сертификат для гостевой системы в случает подтверждения цепочки доверия.

Переработка на языке Rust позволяет добиться более простого и безопасного соблюдения “правила двух“, применяемого Google для поддержания безопасности системных компонентов Android. В соответствии с данным правилом любой добавляемый код должен подпадать не больше, чем под два условия из трёх: работа с непроверенными входными данными, использование небезопасного языка программирования (C/C++) и выполнение с повышенными привилегиями. Из этого правила следует, что код для обработки внешних данных должен либо быть урезан до минимальных привилегий (изолирован), либо быть написан на безопасном языке программирования. По статистике Google около 70% из всех опасных уязвимостей, выявленных в Android, вызваны ошибками при работе с памятью.

Rust сфокусирован на безопасной работе с памятью и позволяет снизить риск появления уязвимостей, вызванных такими проблемами как обращение к области памяти после её освобождения и выход за границы буфера. Безопасная работа с памятью обеспечивается в Rust во время компиляции через проверку ссылок, отслеживание владения объектами и учёт времени жизни объектов (области видимости), а также через оценку корректности доступа к памяти во время выполнения кода. Rust также предоставляет средства для защиты от целочисленных переполнений, требует обязательной инициализации значений переменных перед использованием, лучше обрабатывает ошибки в стандартной библиотеке, применяет концепцию неизменяемости (immutable) ссылок и переменных по умолчанию, предлагает сильную статическую типизацию для минимизации логических ошибок.

Из трудностей, возникающих в процессе разработки на языке Rust низкоуровневых компонентов, таких как драйверы, упоминается необходимость работы с голыми указателями в режиме usafe, так как Rust создан с оглядкой на использование памяти, выделяемой в программе, а в коде, работающем без прослоек поверх железа, приходятся обращаться к разделяемой памяти и MMIO. В настоящее время возможности Rust по работе с голыми указателями оставляют желать лучшего, но ситуация должна измениться после стабилизации поддержки макросов offset_of, slice_ptr_get и slice_ptr_len.

Из недостатков также заслуживает внимания потребность в улучшенном синтаксисе для доступа к полям структур и индексам массивов через голые указатели без создания ссылок, а также ограничения в создании безопасных обвязок над unsafe-операциями, которые могут вызвать неопределённое поведение и не могут быть проверены компилятором. Например, подобные обвязки невозможно создать для операций с таблицами страниц памяти, так как маппинг страниц в одной части программы, может повлиять на другие части.

Что касается размера результирующего кода, то старый вариант прошивки pVM занимал 220 kB, а новый – 460 kB, но при этом в переписанный вариант были добавлены новые возможности, благодаря которым удалось избавиться от некоторых других компонентов, применяемых при загрузке. В итоге, суммарный размер всех старых и новых загрузочных компонентов оказался сопоставим. Отмечается, что когда размер важнее производительности, сопоставимых с языком Си результатов можно добиться включив в компиляторе дополнительные режимы оптимизации по размеру, отбросить лишние зависимости и не использовать средства форматирования строк.

Дополнительно упоминается продолжение работы над реализацией возможности запуска написанных на языке Rust заслуживающих доверия приложений (Trusted Application) в операционной системе Trusty, предоставляющей TEE-окружение (Trusted Execution Environment) для Android, выполняемое параллельно с Android на том же процессоре в отдельном изолированном окружении. Trusty применяется в устройствах Pixel и уже использует Rust в библиотеках и системных компонентах (ядро остаётся на Си).

Release. Ссылка here.