Arm Memory Tagging Extension

Arm v9 引入了 Arm 内存标记扩展 (MTE),它是标记内存的硬件实现

大体上讲,MTE 会使用额外的元数据标记每次内存分配/取消分配。它会将一个标记分配到一个内存位置,然后该位置可以与引用该内存位置的指针相关联。在运行时,CPU 会检查指针和元数据标记是否与每次加载和存储匹配。

在 Android 12 中,内核和用户空间堆内存分配器可以通过元数据来增强每次分配。这有助于检测释放后使用 bug 和缓冲区溢出 bug,这些都是我们代码库中最常见的内存安全 bug 来源。

MTE 操作模式

MTE 有三种操作模式:

  • 同步模式 (SYNC)
  • 异步模式 (ASYNC)
  • 非对称模式 (ASYMM)

同步模式 (SYNC)

此模式针对 bug 检测的正确性(而非性能)进行了优化,可以在可接受较高性能开销的情况下用作精确的 bug 检测工具。启用后,MTE SYNC 可以作为有效的安全缓解措施。 如果标记不匹配,处理器会立即中止执行并终止进程,同时返回 SIGSEGV(代码为 SEGV_MTESERR)以及有关内存访问和故障地址的完整信息。

我们建议在测试期间使用此模式作为 HWASan/KASAN 的替代方案,或者当目标进程出现存在漏洞的受攻击面时在生产环境中使用此模式。此外,当 ASYNC 模式指示存在 bug 时,可以通过使用运行时 API 将执行切换到 SYNC 模式,来获取准确的 bug 报告。

在 SYNC 模式下运行时,Android 分配器会记录所有分配和取消分配的堆栈轨迹,并利用这些信息来提供更好的错误报告,其中包含内存错误的说明(例如“释放后使用”或“缓冲区溢出”)以及相关内存事件的堆栈轨迹。此类报告可提供更多上下文信息,并简化 bug 跟踪和修复流程。

异步模式 (ASYNC)

此模式专门针对 bug 报告的性能(而非精确度)进行了优化,可用于对内存安全 bug 进行低开销检测。
如果标记不匹配,处理器会继续执行,直到达到最近的内核条目(例如,系统调用或计时器中断)。这时,处理器会终止进程并返回 SIGSEGV(代码为 SEGV_MTEAERR),而不会记录错误地址或内存访问。
我们建议在生产环境中对经过严格测试的代码库(已知其内存安全 bug 的密度较低)使用此模式,这将通过在测试期间使用 SYNC 模式来实现。

非对称模式 (ASYMM)

作为 Arm v8.7-A 中的另一项功能,非对称 MTE 模式提供对内存读取的同步检查以及对内存写入的异步检查,其性能类似于 ASYNC 模式。在大多数情况下,此模式更优于 ASYNC 模式,我们建议尽可能使用此模式而不是 ASYNC。

因此,下文中介绍的 API 均未提及非对称模式。而是可以配置操作系统在请求异步模式时始终使用非对称模式。如需了解详情,请参阅“配置特定于 CPU 的首选 MTE 级别”部分。

用户空间中的 MTE

以下部分介绍了如何为系统进程和应用启用 MTE。MTE 默认处于停用状态,除非为特定进程设置了以下选项之一(请参阅下文,了解启用 MTE 的组件)。

使用构建系统启用 MTE

作为一项进程级属性,MTE 可通过主可执行文件的构建时设置来进行控制。以下选项可允许对单个可执行文件或源代码树中的整个子目录更改此设置。对于库或任何既不是可执行文件也不是测试的目标,此设置将被忽略。

1. 在 Android.bp 中,为特定项目启用 MTE(示例):

MTE 模式 设置
异步 MTE
  sanitize: {
  memtag_heap: true,
  }
同步 MTE
  sanitize: {
  memtag_heap: true,
  diag: {
  memtag_heap: true,
  },
  }

或者,在 Android.mk:

MTE 模式 设置
Asynchronous MTE LOCAL_SANITIZE := memtag_heap
Synchronous MTE LOCAL_SANITIZE := memtag_heap
LOCAL_SANITIZE_DIAG := memtag_heap

2. 在源代码树的子目录中使用产品变量启用 MTE:

MTE 模式 包含列表 排除列表
async PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
同步 PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS MEMTAG_HEAP_SYNC_INCLUDE_PATHS

MTE 模式 设置
异步 MTE MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
同步 MTE MEMTAG_HEAP_SYNC_INCLUDE_PATHS

或通过指定可执行文件的排除路径:

MTE 模式 设置
异步 MTE PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
同步 MTE

示例(与 PRODUCT_CFI_INCLUDE_PATHS 的用法类似)

  PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor)
  PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \
                                    vendor/$(vendor)/projectB

使用系统属性启用 MTE

通过设置以下系统属性,可以在运行时替换上述构建设置:

arm64.memtag.process.<basename> = (off|sync|async)

其中,basename 表示可执行文件的基名。

例如,如需将 /system/bin/ping/data/local/tmp/ping 设置为使用异步 MTE,请使用 adb shell setprop arm64.memtag.process.ping async

使用环境变量启用 MTE

如需替换构建设置,另一种方法是定义环境变量:MEMTAG_OPTIONS=(off|sync|async)。如果同时定义了环境变量和系统属性,则以环境变量为准。

为应用启用 MTE

如果未指定 MTE,则应用在默认情况下会停用 MTE。但如果要在应用中使用 MTE,请在 AndroidManifest.xml 中的 <application><process> 标记下设置 android:memtagMode

android:memtagMode=(off|default|sync|async)

当为 <application> 标记设置了该属性时,该属性会影响应用使用的所有进程,不过可以通过设置 <process> 标记来为各个进程替换该属性。

对于实验,可以使用兼容性更改为未在清单中指定任何值(或指定 default)的应用设置 memtagMode 属性的默认值。
您可以在全局设置菜单中的 System > Advanced > Developer options > App Compatibility Changes 下找到这些更改。设置 NATIVE_MEMTAG_ASYNCNATIVE_MEMTAG_SYNC 可为特定应用启用 MTE。
或者,也可以使用 am 命令来进行此设置,如下所示:

$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name

构建 MTE 系统映像

我们强烈建议在开发和调试期间为所有原生二进制文件启用 MTE。这有助于尽早检测内存安全 bug,并提供真实的用户覆盖率(如果在测试 build 中启用)。

我们强烈建议在开发期间为所有原生二进制文件启用同步模式的 MTE

SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m

与构建系统中的任何变量一样,SANITIZE_TARGET 可用作环境变量或 make 设置(例如,在 product.mk 文件中)。
请注意,这将为所有原生进程启用 MTE,但不会为可按上述说明启用 MTE 的应用(从 zygote64 创建的分支)启用 MTE。

配置特定于 CPU 的首选 MTE 级别

在某些 CPU 上,MTE 在 ASYMM 甚至 SYNC 模式下的性能可能与 ASYNC 类似。因此,在请求不太严格的检查模式时,可以对这些 CPU 启用更严格的检查,以便在不牺牲性能的情况下获得更严格检查的错误检测优势。
默认情况下,配置为在 ASYNC 模式下运行的进程将在所有 CPU 上以 ASYNC 模式运行。如需配置内核以在特定 CPU 上以 SYNC 模式运行这些进程,必须在启动时将值同步写入 sysfs 条目 /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred。可以通过 init 脚本来完成此操作。例如,如需将 CPU 0-1 配置为在 SYNC 模式下运行 ASYNC 模式进程,并将 CPU 2-3 配置为在 ASYMM 模式下运行,请将以下内容添加到供应商 init 脚本的 init 子句中:

  write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm
  write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm

在 SYNC 模式下运行的 ASYNC 模式进程的 Tombstone 将包含内存错误位置的精确堆栈轨迹。不过,它们不会包含分配或取消分配堆栈轨迹。只有当进程配置为在 SYNC 模式下运行时,才能使用这些堆栈轨迹。

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

其中,level 为 0 或 1。
在 malloc 中停用内存初始化,并避免更改内存标记(除非是出于正确性需要)。

int mallopt(M_MEMTAG_TUNING, level)

其中,level 为:

  • M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_UAF

选择标记分配策略。

  • 默认设置为 M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_BUFFER_OVERFLOW - 分配不同的标记值给相邻分配以启用针对线性缓冲区上溢和下溢 bug 的确定性检测。此模式检测到释放后使用 bug 的几率略微有所降低,因为对于每个内存位置,只有一半的可能标记值处于可用状态。请注意,MTE 无法检测同一标记粒度(16 字节对齐的分块)内的溢出,即使在此模式下也可能会错失小型溢出。此类溢出不会导致内存损坏,因为一个粒度内的内存绝不会用于多个分配。
  • M_MEMTAG_TUNING_UAF - 启用独立随机标记,可确保检测空间 bug(缓冲区溢出)和时态 bug(释放后使用)达到约 93% 的均匀概率。

除了上述 API 之外,经验丰富的用户可能还需要注意以下事项:

  • 设置 PSTATE.TCO 硬件寄存器可能会暂时抑制标记检查(示例)。 例如,在复制具有未知标记内容的内存范围,或解决热循环中的性能瓶颈时。
  • 使用 M_HEAP_TAGGING_LEVEL_SYNC 时,系统崩溃处理程序会提供额外的信息,例如分配和取消分配堆栈轨迹。 此功能需要访问标记位,并且在设置信号处理程序时传递 SA_EXPOSE_TAGBITS 标志即可启用。对于任何设置了自己的信号处理程序并将未知崩溃委托给系统的程序,也建议执行相同的操作。

内核中的 MTE

如需为内核启用 MTE 加速的 KASAN,请为内核配置 CONFIG_KASAN=yCONFIG_KASAN_HW_TAGS=y。从 Android 12-5.10 开始,GKI 内核会默认启用这些配置。
可在启动时使用以下命令行参数进行控制:

  • kasan=[on|off] - 启用或停用 KASAN(默认值:on
  • kasan.mode=[sync|async] - 选择同步模式或异步模式(默认值:sync
  • kasan.stacktrace=[on|off] - 是否收集堆栈轨迹(默认值:on
    • 堆栈轨迹集合还需要 stack_depot_disable=off
  • kasan.fault=[report|panic] - 是仅输出报告,还是同时也输出内核崩溃(默认值:report)。无论选择哪一个选项,在报告第一个错误后都会停用标记检查。

我们强烈建议在调试、开发和测试期间使用 SYNC 模式。对于所有使用环境变量构建系统的进程,应全局启用此选项。在此模式下,开发者可以在开发过程的早期阶段检测到 bug,代码库会更快进入稳定状态,并避免更晚才在生产环境中检测到 bug。

我们强烈建议在生产环境中使用 ASYNC 模式。这是一种低开销的工具,可用于检测进程中是否存在内存安全 bug,并且可提供进一步的深度防御措施。检测到 bug 后,开发者可以利用运行时 API 切换到 SYNC 模式,并从一组抽样用户处获取准确的堆栈轨迹。

我们强烈建议为 SoC 配置特定于 CPU 的首选 MTE 级别。Asymm 模式通常具有与 ASYNC 相同的性能特征,并且几乎始终是首选模式。采用顺序执行的小核在所有三种模式下通常表现出类似的性能,并且可以配置为采用 SYNC 作为首选模式。

开发者应通过检查 /data/tombstoneslogcat 或监控供应商 DropboxManager 管线是否存在最终用户 bug 来检查是否发生了系统崩溃。如需详细了解如何调试 Android 原生代码,请参阅此处的相关信息。

已启用 MTE 的平台组件

在 Android 12 中,许多安全关键系统组件均使用 MTE ASYNC 来检测最终用户崩溃,并提供一个额外的深度防御层。这些组件包括:

  • 网络守护程序和实用程序(netd 除外)
  • 蓝牙、SecureElement、NFC HAL 和系统应用
  • statsd 守护程序
  • system_server
  • zygote64(可允许应用选择启用 MTE)

这些目标的选择基于以下条件:

  • 特权进程(即具备 unprivileged_app SELinux 域所不具备的访问权限的进程)
  • 处理不可信的输入(规则二
  • 可接受的性能下降(此性能下降不会造成用户可见的延迟)

我们鼓励供应商按照上述标准在生产环境中为更多组件启用 MTE。在开发期间,我们建议使用 SYNC 模式测试这些组件,以检测可轻松修复的 bug 并评估 ASYNC 对其性能的影响。
未来,Android 计划根据即将推出的硬件设计的性能特征来扩充启用 MTE 的系统组件列表。