Почему МТЕ?
Ошибки безопасности памяти, которые представляют собой ошибки в работе с памятью на родных языках программирования, являются распространенными проблемами кода. Они приводят к уязвимостям безопасности, а также к проблемам со стабильностью.
Armv9 представил расширение маркировки памяти Arm (MTE), аппаратное расширение, которое позволяет вам обнаруживать ошибки использования после освобождения и переполнения буфера в вашем собственном коде.
Проверьте поддержку
Начиная с Android 13, некоторые устройства поддерживают MTE. Чтобы проверить, работает ли ваше устройство с включенным MTE, выполните следующую команду:
adb shell grep mte /proc/cpuinfo
Если результат — « Features : [...] mte
, ваше устройство работает с включенным MTE.
Некоторые устройства не включают MTE по умолчанию, но позволяют разработчикам перезагружаться с включенным MTE. Это экспериментальная конфигурация, которую не рекомендуется использовать в обычном режиме, поскольку она может снизить производительность или стабильность устройства, но может быть полезна для разработки приложений. Чтобы получить доступ к этому режиму, перейдите в «Параметры разработчика» > «Расширение тегов памяти» в приложении «Настройки». Если эта опция отсутствует, ваше устройство не поддерживает включение 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 во время тестирования.
Включить МТЕ
Для одного устройства
В качестве эксперимента можно использовать изменения совместимости приложений, чтобы установить значение атрибута memtagMode
по умолчанию для приложения, которое не указывает никакого значения в манифесте (или указывает "default"
).
Их можно найти в разделе «Система» > «Дополнительно» > «Параметры разработчика» > «Изменения совместимости приложений» в меню глобальных настроек. Установка NATIVE_MEMTAG_ASYNC
или NATIVE_MEMTAG_SYNC
включает MTE для конкретного приложения.
Альтернативно это можно установить с помощью команды am
следующим образом:
- Для режима SYNC:
$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
- Для режима ASYNC:
$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
В Градле
Вы можете включить 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
вашего манифеста с синхронизацией для отладочных сборок.
Альтернативно вы можете включить MTE для всех сборок пользовательского типа сборки. Для этого создайте свой собственный buildType и поместите 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-сборка
В вашем файле 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
для SYNC или SEGV_MTEAERR
для ASYNC):
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.