Arm v9 引入了 Arm 内存标记扩展 (MTE),这是一种标记内存的 硬件实现。
从宏观层面来看,MTE 会使用额外的元数据标记每个内存分配/释放操作。它会为内存位置分配一个标记,然后该标记可以与引用该内存位置的指针关联。在运行时,CPU 会检查指针和元数据标记在每次加载和存储时是否匹配。
在 Android 12 中,内核和用户空间堆内存分配器可以使用元数据扩充每次分配。这有助于检测释放后使用和缓冲区溢出错误,这些错误是我们代码库中最常见的内存安全错误来源。
MTE 运行模式
MTE 具有三种运行模式
- 同步模式 (SYNC)
- 异步模式 (ASYNC)
- 非对称模式 (ASYMM)
同步模式 (SYNC)
此模式针对错误检测的正确性而非性能进行了优化,并且在可以接受更高的性能开销时,可以用作精确的错误检测工具。启用后,MTE SYNC 充当安全缓解措施。如果标记不匹配,处理器会立即中止执行并终止进程,并显示 SIGSEGV
代码 (SEGV_MTESERR
) 以及有关内存访问和错误地址的完整信息。
我们建议在测试期间使用此模式,以替代 HWASan/KASAN,或者在生产环境中目标进程代表易受攻击的攻击面时使用。此外,当 ASYNC 模式指示存在错误时,可以使用运行时 API 将执行切换到 SYNC 模式,从而获得准确的错误报告。
在 SYNC 模式下运行时,Android 分配器会记录所有分配和释放操作的堆栈轨迹,并使用它们来提供更好的错误报告,其中包括对内存错误(例如释放后使用或缓冲区溢出)的解释以及相关内存事件的堆栈轨迹。此类报告提供更多上下文信息,使错误更易于跟踪和修复。
异步模式 (ASYNC)
此模式针对性能而非错误报告的准确性进行了优化,并且可以用作内存安全错误的低开销检测。
如果标记不匹配,处理器会继续执行,直到最近的内核入口点(例如,系统调用或计时器中断),然后终止进程,并显示 SIGSEGV
代码 (SEGV_MTEAERR
),但不记录错误地址或内存访问。
我们建议在生产环境中经过充分测试的代码库中使用此模式,因为已知这些代码库的内存安全错误密度较低,这可以通过在测试期间使用 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 模式 | 设置 |
---|---|
异步 MTE |
LOCAL_SANITIZE := memtag_heap |
同步 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 |
sync | 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 的应用可以通过在 AndroidManifest.xml
的 <application>
或 <process>
标记下设置 android:memtagMode
来启用 MTE。
android:memtagMode=(off|default|sync|async)
在 <application>
标记上设置时,该属性会影响应用使用的所有进程,并且可以通过设置 <process>
标记来替换单个进程的属性。
对于实验,可以使用兼容性变更为未在清单中指定任何值(或指定 default
)的应用设置 memtagMode
属性的默认值。
这些可以在全局设置菜单中的 系统 > 高级 > 开发者选项 > 应用兼容性变更
下找到。设置 NATIVE_MEMTAG_ASYNC
或 NATIVE_MEMTAG_SYNC
可为特定应用启用 MTE。
或者,可以使用 am
命令进行设置,如下所示
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
构建 MTE 系统映像
我们强烈建议在开发和启动期间在所有原生二进制文件中启用 MTE。如果测试版本中启用了此选项,则有助于尽早检测到内存安全错误并提供真实的用户覆盖率。
我们强烈建议在开发期间在所有原生二进制文件中以同步模式启用 MTE
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
与构建系统中的任何变量一样,SANITIZE_TARGET
可以用作环境变量或 make
设置(例如,在 product.mk
文件)中。
请注意,这会为所有原生进程启用 MTE,但不会为应用(从 zygote64
分叉)启用 MTE,对于应用,可以按照上述说明启用 MTE。
配置 CPU 特定的首选 MTE 级别
在某些 CPU 上,ASYMM 甚至 SYNC 模式下 MTE 的性能可能与 ASYNC 模式的性能相似。这使得在请求不太严格的检查模式时,在这些 CPU 上启用更严格的检查变得值得,以便在没有性能下降的情况下获得更严格检查的错误检测优势。
默认情况下,配置为在 ASYNC 模式下运行的进程将在所有 CPU 上以 ASYNC 模式运行。要配置内核以在特定 CPU 上以 SYNC 模式运行这些进程,必须在启动时将值 sync 写入 sysfs
条目 /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
。这可以使用 init 脚本完成。例如,要将 CPU 0-1 配置为以 SYNC 模式运行 ASYNC 模式进程,并将 CPU 2-3 配置为使用非对称模式运行,可以将以下内容添加到供应商 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
- 通过为相邻分配分配不同的标记值,实现对线性缓冲区溢出和下溢错误的确定性检测。此模式检测释放后使用错误的可能性略有降低,因为每个内存位置只有一半的可能标记值可用。请注意,MTE 无法检测同一标记粒度(16 字节对齐的块)内的溢出,并且即使在此模式下也可能遗漏小溢出。此类溢出不会导致内存损坏,因为一个粒度内的内存永远不会用于多次分配。M_MEMTAG_TUNING_UAF
- 为空间(缓冲区溢出)和时间(释放后使用)错误检测启用独立随机标记,以实现约 93% 的统一概率。
除了上述 API 之外,经验丰富的用户可能还想了解以下内容
- 设置
PSTATE.TCO
硬件寄存器可以暂时禁止标记检查 (示例)。例如,当复制具有未知标记内容的内存范围时,或解决热循环中的性能瓶颈时。 - 使用
M_HEAP_TAGGING_LEVEL_SYNC
时,系统崩溃处理程序会提供额外信息,例如分配和释放堆栈轨迹。此功能需要访问标记位,并通过在设置信号处理程序时传递SA_EXPOSE_TAGBITS
标志来启用。建议任何设置了自己的信号处理程序并将未知崩溃委派给系统信号处理程序的程序都这样做。
内核中的 MTE
要为内核启用 MTE 加速的 KASAN,请使用 CONFIG_KASAN=y
、CONFIG_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 模式。应使用环境变量或构建系统为所有进程全局启用此选项。在此模式下,错误会在开发过程的早期被检测到,代码库会更快地稳定下来,并且避免了在生产环境中稍后检测到错误的成本。
我们强烈建议在生产环境中使用 ASYNC 模式。这提供了一种低开销工具,用于检测进程中是否存在内存安全错误以及进一步的纵深防御。检测到错误后,开发者可以利用运行时 API 切换到 SYNC 模式,并从抽样用户集中获得准确的堆栈轨迹。
我们强烈建议为 SoC 配置 CPU 特定的首选 MTE 级别。Asymm 模式通常具有与 ASYNC 相同的性能特征,并且几乎总是比 ASYNC 更可取。小型有序内核在所有三种模式下通常表现出相似的性能,并且可以配置为首选 SYNC。
开发者应通过检查 /data/tombstones
、logcat
或通过监控供应商 DropboxManager
管道来检查最终用户错误是否存在崩溃。如需详细了解如何调试 Android 原生代码,请参阅此处的信息。
启用 MTE 的平台组件
在 Android 12 中,许多安全关键型系统组件使用 MTE ASYNC 来检测最终用户崩溃并充当额外的纵深防御层。这些组件包括
- 联网守护程序和实用程序(
netd
除外) - 蓝牙、SecureElement、NFC HAL 和系统应用
statsd
守护程序system_server
zygote64
(允许应用选择加入使用 MTE)
这些目标是根据以下标准选择的
- 特权进程(定义为可以访问 unprivileged_app SELinux 域无法访问的内容的进程)
- 处理不可信输入的进程 (二规则)
- 可接受的性能降低(降低速度不会产生用户可见的延迟)
我们鼓励供应商在生产环境中为更多组件启用 MTE,并遵循上述标准。在开发过程中,我们建议使用 SYNC 模式测试这些组件,以检测易于修复的错误,并评估 ASYNC 对其性能的影响。
未来,Android 计划根据即将推出的硬件设计的性能特征,扩展启用 MTE 的系统组件列表。