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

По состоянию на 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/проектов медиа-компонентов, таких как /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 в make-файлах

Чтобы включить 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:

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

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

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

Отключение CFI

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

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

Проверка

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