Целостность потока управления

По состоянию на 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 . Он также напрямую включен в make-файлах/файлах шаблонов набора медиакомпонентов, таких как /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-файлы или файлы blueprint.

Поддержка CFI в make-файлах

Чтобы включить CFI в файле makefile, например, /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:

Ещё одна возможная проблема — попытка включить CFI в коде, содержащем косвенные вызовы ассемблерного кода. Поскольку ассемблерный код не типизирован, это приводит к несоответствию типов.

Чтобы исправить это, создайте обертки для нативного кода для каждого вызова ассемблерного кода и присвойте оберткам ту же сигнатуру функции, что и вызывающий указатель. После этого обертка сможет напрямую вызывать ассемблерный код. Поскольку прямые переходы не инструментируются CFI (их нельзя перенаправить во время выполнения, и поэтому они не представляют угрозы безопасности), это решит проблему.

Если функций ассемблера слишком много, и все их невозможно исправить, можно также внести в черный список все функции, содержащие косвенные вызовы ассемблерных функций. Это не рекомендуется, поскольку отключает проверки CFI для этих функций, тем самым открывая поверхность атаки.

Отключение CFI

Мы не обнаружили никаких накладных расходов на производительность, поэтому вам не нужно отключать CFI. Однако, если это повлияет на пользователя, вы можете выборочно отключить CFI для отдельных функций или исходных файлов, предоставив файл черного списка для проверки корректности во время компиляции. Черный список указывает компилятору отключить инструментацию CFI в указанных местах.

Система сборки Android поддерживает черные списки для каждого компонента (позволяя выбирать исходные файлы или отдельные функции, которые не будут получать инструментарий CFI) как для Make, так и для Soong. Более подробную информацию о формате файла черного списка см. в документации Clang .

Валидация

В настоящее время нет специальных тестов CTS для CFI. Вместо этого убедитесь, что тесты CTS проходят успешно как с включенным, так и с выключенным CFI, чтобы проверить, не влияет ли CFI на работу устройства.