Расширение тегов памяти Arm (MTE)

Почему МТЕ?

Ошибки безопасности памяти, которые представляют собой ошибки в работе с памятью на родных языках программирования, являются распространенными проблемами кода. Они приводят к уязвимостям безопасности, а также к проблемам со стабильностью.

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.