Почему именно MTE?
Ошибки в работе с памятью, то есть ошибки в обработке памяти в нативных языках программирования, являются распространенными проблемами в коде. Они приводят к уязвимостям в системе безопасности, а также к проблемам со стабильностью.
В Armv9 было представлено расширение Arm Memory Tagging Extension (MTE), аппаратное расширение, позволяющее выявлять ошибки использования памяти после освобождения (use-after-free) и переполнения буфера (buffer-overflow) в нативном коде.
Проверьте наличие поддержки.
Начиная с Android 13, поддержка MTE появилась на некоторых устройствах. Чтобы проверить, включена ли поддержка MTE на вашем устройстве, выполните следующую команду:
adb shell grep mte /proc/cpuinfo
Если в результате отображается Features : [...] mte , значит, на вашем устройстве включена функция MTE.
На некоторых устройствах функция MTE по умолчанию отключена, но разработчики могут перезагрузить устройство с включенной функцией MTE. Это экспериментальная конфигурация, которая не рекомендуется для обычного использования, поскольку может снизить производительность или стабильность устройства, но может быть полезна для разработки приложений. Чтобы получить доступ к этому режиму, перейдите в раздел «Параметры разработчика» > «Расширение тегирования памяти» в приложении «Настройки». Если этот параметр отсутствует, ваше устройство не поддерживает включение MTE таким способом.
Устройства с поддержкой MTE
Известно, что следующие устройства поддерживают технологию MTE:
- Pixel 8 (Shiba)
- Pixel 8 Pro (Husky)
- Pixel 8a (Akita)
- Pixel 9 (Токай)
- Pixel 9 Pro (Кайман)
- Pixel 9 Pro XL (Komodo)
- Pixel 9 Pro Fold (Comet)
- Pixel 9a (Tegu)
режимы работы MTE
MTE поддерживает два режима: SYNC и ASYNC. Режим SYNC предоставляет более полную диагностическую информацию и поэтому больше подходит для целей разработки, в то время как режим ASYNC обладает высокой производительностью, что позволяет включать его в выпущенные приложения.
Синхронный режим (SYNC)
Этот режим оптимизирован для удобства отладки, а не для повышения производительности, и может использоваться в качестве точного инструмента обнаружения ошибок, когда допустимы более высокие накладные расходы на производительность. При включении MTE SYNC также выступает в качестве средства защиты.
При несоответствии тегов процессор завершает процесс на некорректной инструкции загрузки или сохранения с кодом SIGSEGV (с кодом si_code SEGV_MTESERR) и полной информацией об обращении к памяти и адресе ошибки.
Этот режим полезен во время тестирования в качестве более быстрой альтернативы HWASan , не требующей перекомпиляции кода, или в производственной среде, когда ваше приложение представляет собой уязвимую поверхность для атак. Кроме того, если в режиме ASYNC (описанном ниже) обнаружена ошибка, можно получить точный отчет об ошибке, используя API среды выполнения для переключения выполнения в режим SYNC.
Кроме того, при работе в режиме синхронизации (SYNC) распределитель памяти Android записывает трассировку стека каждого выделения и освобождения памяти и использует её для предоставления более подробных отчётов об ошибках, включающих объяснение ошибки памяти, такой как использование памяти после освобождения или переполнение буфера, а также трассировки стека соответствующих событий памяти (подробнее см. в разделе «Понимание отчётов MTE» ). Такие отчёты предоставляют больше контекстной информации и упрощают отслеживание и исправление ошибок по сравнению с режимом асинхронности (ASYNC).
Асинхронный режим (ASYNC)
Этот режим оптимизирован для повышения производительности, а не точности отчетов об ошибках, и может использоваться для обнаружения ошибок безопасности памяти с минимальными накладными расходами. При несоответствии тегов процессор продолжает выполнение до ближайшего события ядра (например, системного вызова или прерывания таймера), где он завершает процесс с кодом SIGSEGV (код SEGV_MTEAERR), не регистрируя адрес ошибки или обращение к памяти.
Этот режим полезен для снижения уязвимостей, связанных с безопасностью памяти, в производственной среде на хорошо протестированных кодовых базах, где, как известно, плотность ошибок, связанных с безопасностью памяти, низка, что достигается за счет использования режима SYNC во время тестирования.
Включить MTE
Для одного устройства
Для экспериментов можно использовать изменения совместимости приложений, чтобы установить значение по умолчанию для атрибута memtagMode для приложения, которое не указывает никакого значения в манифесте (или указывает "default" ).
Эти параметры можно найти в глобальном меню настроек в разделе Система > Дополнительно > Параметры разработчика > Изменения совместимости приложений. Установка параметров NATIVE_MEMTAG_ASYNC или NATIVE_MEMTAG_SYNC включает MTE для конкретного приложения.
В качестве альтернативы, это можно установить с помощью команды am следующим образом:
- Для режима синхронизации:
$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name - Для асинхронного режима:
$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
В Gradle
Вы можете включить MTE для всех отладочных сборок вашего проекта Gradle, добавив следующий код:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>
в файл app/src/debug/AndroidManifest.xml . Это заменит memtagMode в вашем манифесте на sync для отладочных сборок.
В качестве альтернативы вы можете включить MTE для всех сборок пользовательского типа сборки. Для этого создайте свой собственный тип сборки и поместите XML-код в файл app/src/<name of buildType>/AndroidManifest.xml .
Для установки APK-файла на любое совместимое устройство.
MTE отключен по умолчанию. Приложения, желающие использовать MTE, могут сделать это, установив android:memtagMode в теге <application> или <process> в файле AndroidManifest.xml .
android:memtagMode=(off|default|sync|async)
Если этот атрибут установлен в теге <application> , он влияет на все процессы, используемые приложением, и может быть переопределен для отдельных процессов путем установки тега <process> .
Создание с использованием измерительных приборов.
Включение MTE, как объяснялось ранее, помогает обнаруживать ошибки повреждения памяти в нативной куче. Для обнаружения повреждения памяти в стеке, помимо включения MTE для приложения, необходимо пересобрать код с инструментацией. Полученное приложение будет работать только на устройствах, поддерживающих MTE .
Для компиляции нативного (JNI) кода вашего приложения с помощью MTE выполните следующие действия:
ndk-build
В файле Application.mk :
APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag
CMake
Для каждой цели в вашем файле CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)
Запустите ваше приложение
После включения MTE используйте и тестируйте приложение в обычном режиме. Если будет обнаружена проблема с безопасностью памяти, приложение завершится с ошибкой, которая будет выглядеть примерно так (обратите внимание на SIGSEGV с SEGV_MTESERR для синхронной работы или SEGV_MTEAERR для асинхронной работы):
pid: 13935, tid: 13935, name: sanitizer-statu >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0
x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021
x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030
x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000
x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78
x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000
x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000
x28 0000000000000000 x29 0000007fe8191b70
lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000
backtrace:
#00 pc 00000000000010c0 /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#01 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#02 pc 00000000000019cc /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000487d8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
deallocated by thread 13935:
#00 pc 000000000004643c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 00000000000421e4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 00000000000010b8 /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
allocated by thread 13935:
#00 pc 0000000000042020 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 0000000000042394 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 000000000003cc9c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#03 pc 00000000000010ac /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#04 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report
Более подробную информацию см. в разделе «Понимание отчетов MTE» в документации AOSP. Вы также можете отлаживать свое приложение с помощью Android Studio , и отладчик остановится на строке, вызывающей недопустимый доступ к памяти.
Опытные пользователи: Использование MTE в собственном распределителе памяти
Для использования MTE для памяти, не выделенной с помощью обычных системных распределителей памяти, необходимо модифицировать распределитель памяти, чтобы он помечал память и указатели.
Страницы для вашего распределителя памяти необходимо выделять, используя флаг PROT_MTE в параметре prot команды mmap (или mprotect ).
Все выделенные ресурсы с метками должны быть выровнены по 16 байтам, поскольку метки могут быть присвоены только для 16-байтовых блоков (также известных как гранулы).
Затем, перед возвратом указателя, необходимо использовать инструкцию IRG для генерации случайного тега и сохранить его в указателе.
Для маркировки используемой памяти воспользуйтесь следующими инструкциями:
-
STG: пометить один 16-байтовый гранулярный фрагмент -
ST2G: пометить два 16-байтовых гранула -
DC GVA: кэш-линия с тем же тегом.
В качестве альтернативы, следующие инструкции также инициализируют память нулями:
-
STZG: пометить и инициализировать нулями один 16-байтовый гранулярный массив. -
STZ2G: пометить и инициализировать нулями два 16-байтовых гранулы -
DC GZVA: метка и обнуление кэш-линии с использованием одной и той же метки.
Обратите внимание, что эти инструкции не поддерживаются на старых процессорах, поэтому их необходимо запускать при условии включения MTE. Вы можете проверить, включен ли MTE для вашего процесса:
#include <sys/prctl.h>
bool runningWithMte() {
int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
return mode != -1 && mode & PR_MTE_TCF_MASK;
}
Возможно, реализация scudo окажется для вас полезной в качестве справочного материала.
Узнать больше
Более подробную информацию можно найти в руководстве пользователя MTE для ОС Android, разработанном компанией Arm.