На днях компания Google сделала важный шаг, объявив о включении языка программирования Rust в число языков, которые допускаются для разработки платформы Android. Да, еще в 2019 году компилятор Rust включили в дерево исходных текстов Android, но это была экспериментальная поддержка.
Сейчас в Android планируется добавить первые компоненты на Rust, это будут новые реализации механизма межпроцессного взаимодействия Binder и Bluetooth-стека. Все это хорошо, но зачем весь этот сыр-бор с включением Rust?
По словам представителей Google, Rust добавили в список языков разработки Android для усиления защищенности последнего, плюс для продвижения приемов безопасного программирования и повышения выявления проблем при работе с памятью в Android. Около 70% из всех опасных уязвимостей, которые выявлены в Android, вызваны ошибками при работе с памятью. Использование Rust дает возможность снизить риск появления уязвимостей, которые вызваны ошибками при работе с памятью, включая обращение к области памяти после ее освобождения и выход за границы буфера.
Безопасная работа с памятью обеспечивается в Rust во время компиляции посредством проверки ссылок, отслеживания владения объектами и учета времени жизни объектов (области видимости). Дополнительно — через оценку корректности доступа к памяти во время выполнения кода. Кроме того, Rust предоставляет средства защиты от целочисленных переполнений и требует обязательной инициализации значений переменных перед использованием. А еще он лучше обрабатывает ошибки в стандартной библиотеке и применяет концепцию неизменяемости (immutable) ссылок и переменных по умолчанию, предлагает сильную статическую типизацию для минимизации логических ошибок.
Что касается Android, то здесь безопасная работа с памятью обеспечивается в поддерживаемых языках Kotlin и Java. Правда, они не подходят для разработки системных компонентов из-за больших накладных расходов. Rust позволяет добиться увеличения производительности до близкой к языкам C и С++. А это значит, что язык можно использовать для разработки низкоуровневых частей платформы и компонентов для взаимодействия с оборудованием.
Безопасность кода на С и С++ в Android обеспечивается благодаря sandbox-изоляции, статическому анализу и fuzzing-тестированию. Возможности изоляции, правда, ограничены — они достигли предела возможностей. Эта ограниченность вызывает рост накладных расходов и увеличение объемов потребляемой памятью, что вызвано необходимостью порождения новых процессов. Плюс есть издержки, которые связаны с использованием IPC.
Sandbox не устраняет уязвимости в коде, все это работает иначе — снижаются риски и усложняется проведение атаки. К сожалению, для того чтобы бороться с проблемами максимально эффективно, нужно знать их все или подавляющую часть. А в ходе тестирования для выявления ошибок нужно создавать условия для их появления. Соответственно, абсолютно все условия предусмотреть невозможно, так что многие ошибки проходят мимо внимания разработчиков.
Компания Google использует так называемое «правило двух», в соответствии с которым для системных процессов любой добавляемый код должен подпадать не больше, чем под два условия из трех:
Работа с непроверенными входными данными.
Использование небезопасного языка программирования.
Выполнение процесса без жесткой sandbox-изоляции.
Соответственно, код для обработки внешних данных должен быть либо урезан до минимальных привилегий, либо написан на безопасном языке программирования.
Можно было бы подумать, что Google планирует переписать на Rust уже имеющийся C\C++ код, но нет — его будут использовать для разработки нового кода. В этом есть смысл, поскольку, согласно статистике разработки, большая часть ошибок появляется либо в новом, либо недавно модифицированном коде. Так, около 50% ошибок работы с памятью в Android OS выявляются как раз в коде, который написан менее года назад.
amarao
Ядро Linux:
DOOM?
lain8dono
Да. Согласно этой терминологии так и есть.
amarao
… При том, что в том же Rust'е вызов naked function является unsafe на 100%, а вещи, которые в naked function творят, к безопасности отношения не имеют, я не уверен, что замена в этом месте C/C++ на Rust что-то фундаментально поменяет. А как писать ядро без naked functions — я очень хочу посмотреть.
mkpankov
В отличие от Си, в Расте аудит UB требуется только в unsafe коде. Которого очень мало — в т.ч. учитывая текущую практику разработки системных компонентов или операционных систем.
Например, в сетевом стеке Fuchsia:
К сожалению, именно число строк посчитать уже сложно. Но порядок и так видно, если на 300 тысяч строк кода 300 мест, где могут быть проблемы.
lain8dono
Легко. Вызываем обычные функции из асма. Я так делал, загляните в мои статеечки.
С другой стороны не очень понятна мысль про
unsafe
. Разница как раз в этом месте и будет фундаментальной. Блокunsafe
предполагает, что внутри вы вручную налагаете ограничения для соответствия всем правилам safe-абстракций. Ну и традиционное:unsafe
не отключает никакие проверки, которые Rust делает.amarao
Э… Простите, а как вы будете уговаривать процессор вызывать "обычные функции" в обработчике прерываний? Там задача — ничего не поломать у прерванного процесса, и без naked functions — никуда. см rust-rfcs/1201.
lain8dono
Легко и непринуждённо поговорю с процессором на его ассемблере. Читайте про такую штуку, как ABI. Я при помощи
extern
говорю функции из Rust следовать стабильному ABI (а не своему внутреннему) и в ассемблере просто выполняю требования ABI. При этомextern
сам по себе не требуетunsafe
. Вообще говоря можно полностью написать ядро без единогоunsafe
. Всё останется корректным. Просто это не столь удобно.А неудобно это именно по той причине, что я теряю мощную фундаментальную абстракцию под названием
unsafe
.gxcreator
А разве extern это не unsafe?
Morgan_iv
extern бывает как из Rust наружу, так и снаружи в Rust. Первое не требует unsafe, второе требует
mkpankov
Вообще да, если посмотреть на статистику нахождения там багов и уязвимостей, в т.ч. вызванных проблемами небезопасной работы с памятью.
Например, вот относительно недавнее исследование. Оно не блещет полнотой, но достаточно репрезентативно.