По состоянию на 2016 год около 86% всех уязвимостей Android связаны с безопасностью памяти. Большинство уязвимостей эксплуатируется злоумышленниками, изменяющими обычный поток управления приложения для выполнения произвольных вредоносных действий со всеми привилегиями эксплуатируемого приложения. Целостность потока управления (CFI) — это механизм безопасности, запрещающий внесение изменений в исходный граф потока управления скомпилированного двоичного файла, что значительно затрудняет проведение подобных атак.
В Android 8.1 мы включили реализацию CFI в LLVM в медиастеке. В Android 9 мы включили CFI в большем количестве компонентов, а также в ядре. Системный CFI включён по умолчанию, но необходимо включить CFI ядра.
CFI в LLVM требует компиляции с использованием оптимизации во время компоновки (LTO) . LTO сохраняет представление объектных файлов в битовом коде LLVM до момента компоновки, что позволяет компилятору лучше оценить возможные оптимизации. Включение LTO уменьшает размер финального двоичного файла и повышает производительность, но увеличивает время компиляции. При тестировании на Android сочетание LTO и CFI приводит к незначительному увеличению размера кода и производительности; в некоторых случаях оба показателя улучшаются.
Более подробную техническую информацию о CFI и о том, как обрабатываются другие проверки прямого управления, см. в документации по проектированию LLVM .
Примеры и источники
CFI предоставляется компилятором и добавляет инструментарий в исполняемый файл во время компиляции. Мы поддерживаем CFI в цепочке инструментов Clang и системе сборки Android в AOSP.
CFI по умолчанию включен для устройств Arm64 для набора компонентов в файле /platform/build/target/product/cfi-common.mk
. Он также напрямую включен в наборе файлов makefile/blueprint компонентов мультимедиа, таких как /platform/frameworks/av/media/libmedia/Android.bp
и /platform/frameworks/av/cmds/stagefright/Android.mk
.
Внедрение системы CFI
CFI включен по умолчанию, если вы используете Clang и систему сборки Android. Поскольку CFI обеспечивает безопасность пользователей Android, отключать его не следует.
Фактически, мы настоятельно рекомендуем вам включить CFI для дополнительных компонентов. Идеальными кандидатами являются привилегированный нативный код или нативный код, обрабатывающий ненадёжный пользовательский ввод. Если вы используете clang и систему сборки Android, вы можете включить CFI в новых компонентах, добавив несколько строк в make-файлы или файлы чертежей.
Поддержка CFI в makefiles
Чтобы включить CFI в make-файле, например /platform/frameworks/av/cmds/stagefright/Android.mk
, добавьте:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
-
LOCAL_SANITIZE
указывает CFI в качестве дезинфицирующего средства во время сборки. -
LOCAL_SANITIZE_DIAG
включает режим диагностики для CFI. В этом режиме в logcat выводится дополнительная отладочная информация при сбоях, что полезно при разработке и тестировании сборок. Однако обязательно отключите режим диагностики в производственных сборках. -
LOCAL_SANITIZE_BLACKLIST
позволяет компонентам выборочно отключать инструментарий CFI для отдельных функций или исходных файлов. Чёрный список можно использовать в качестве крайней меры для устранения любых проблем, с которыми сталкивается пользователь. Подробнее см. в разделе Отключение CFI .
Поддержка CFI в файлах чертежей
Чтобы включить CFI в файле проекта, например /platform/frameworks/av/media/libmedia/Android.bp
, добавьте:
sanitize: { cfi: true, diag: { cfi: true, }, blacklist: "cfi_blacklist.txt", },
Поиск неисправностей
При включении CFI в новых компонентах вы можете столкнуться с несколькими проблемами, связанными с ошибками несоответствия типов функций и ошибками несоответствия типов кода сборки .
Ошибки несоответствия типов функций возникают из-за того, что CFI ограничивает косвенные вызовы переходом только к функциям с тем же динамическим типом, что и статический тип, используемый в вызове. CFI ограничивает вызовы виртуальных и невиртуальных функций-членов переходом только к объектам, производным от статического типа объекта, используемого для вызова. Это означает, что при наличии кода, нарушающего любое из этих предположений, инструментирование, добавляемое CFI, прервёт выполнение. Например, трассировка стека выдаёт сигнал SIGABRT, а logcat содержит строку о несоответствии, обнаруженном при проверке целостности потока управления.
Чтобы исправить это, убедитесь, что вызываемая функция имеет тот же тип, который был статически объявлен. Вот два примера CL:
- Bluetooth : /c/platform/system/bt/+/532377
- NFC : /c/platform/system/nfc/+/527858
Другая возможная проблема — попытка включить CFI в коде, содержащем косвенные вызовы ассемблера. Поскольку ассемблерный код не типизирован, это приводит к несоответствию типов.
Чтобы исправить это, создайте обёртки нативного кода для каждого вызова сборки и присвойте им ту же сигнатуру функции, что и вызывающему указателю. После этого обёртка сможет напрямую вызывать код сборки. Поскольку прямые ветвления не инструментируются CFI (они не могут быть перенаправлены во время выполнения и, следовательно, не представляют угрозы безопасности), это решит проблему.
Если функций сборки слишком много и исправить их все невозможно, можно также занести в черный список все функции, содержащие косвенные вызовы сборки. Это не рекомендуется, так как это отключает проверки CFI для этих функций, открывая тем самым поверхность для атак.
Отключение CFI
Мы не наблюдали снижения производительности, поэтому отключать CFI не требуется. Однако, если это влияет на работу пользователя, вы можете выборочно отключить CFI для отдельных функций или исходных файлов, указав файл чёрного списка санитайзеров во время компиляции. Этот чёрный список указывает компилятору на необходимость отключения инструментария CFI в определённых местах.
Система сборки Android поддерживает чёрные списки для каждого компонента (позволяя выбирать исходные файлы или отдельные функции, которые не будут подвергаться инструментированию CFI) как для Make, так и для Soong. Подробнее о формате файла чёрного списка см. в документации по Clang .
Проверка
В настоящее время не существует теста CTS, специально предназначенного для CFI. Вместо этого убедитесь, что тесты CTS проходят как с включённым CFI, так и без него, чтобы убедиться, что CFI не влияет на устройство.