从 Android 11 开始,对于 64 位进程,所有堆分配都具有一个由实现定义的标记,该标记在具有对 ARM Top-byte Ignore (TBI) 的内核支持的设备上的指针顶部字节中设置。在回收期间检查该标记时,任何修改此标记的应用都会被终止。对于未来支持 ARM 内存标记扩展 (MTE) 的硬件来说,这是必需的。
Top-byte Ignore
ARM 的 Top-byte Ignore 功能适用于所有 Armv8 AArch64 硬件中的 64 位代码。此功能意味着硬件在访问内存时会忽略指针的顶部字节。
TBI 需要一个兼容的内核,以便正确处理从用户空间传递的已加标记的指针。 4.14 (Pixel 4) 及更高版本中的 Android 通用内核具有必需的 TBI 补丁程序。
在内核中支持 TBI 的设备在进程启动时会被动态检测到,并且对于所有堆分配,都会在指针顶部字节中插入一个依赖于实现的标记。之后,系统会运行一项检查,以确保在回收内存时,相应标记没有被截断。
内存标记扩展就绪情况
ARM 的内存标记扩展 (MTE) 可以帮助解决内存安全问题。MTE 的工作原理是对堆栈、堆和全局变量上的每次内存分配的第 56 到 59 个地址位加标记。硬件和指令集会自动检查每次访问内存时是否使用了正确的标记。
在指针顶部字节中错误存储信息的 Android 应用一定会在启用了 MTE 的设备上中断。利用加标记的指针,可以在 MTE 设备可用之前更轻松地检测并拒绝对指针顶部字节的错误使用。
开发者支持
如果应用崩溃,并且您收到包含此链接的提示,这可能意味着存在以下某种情况:
- 应用尝试释放系统堆分配器未分配的指针。
- 应用中的某个部分修改了指针的顶部字节。不能修改指针的顶部字节,您需要更改代码来修复此问题。
指针的顶部字节被错误使用或修改的示例包括:
- 指向特定类型的指针将特定于应用的元数据存储在前 16 个地址位中。
- 指针的类型转换为双精度,然后又恢复为原来的类型,因此丢失了较低的地址位。
- 代码计算不同堆栈帧的局部变量地址之间的差异,作为测量递归深度的方法。
某些应用可能依赖于在设置指针的顶部字节后行为不正确的库。我们认识到,快速修复库中的这些底层问题可能非常重要。因此,使用 targetSdkLevel < 30
的应用默认不会启用指针标记。我们还为使用 targetSdkLevel >= 30
构建的应用提供了一个缓解办法以便过渡。
这个办法就是在 AndroidManifest.xml
文件中添加以下内容:
<application android:allowNativeHeapPointerTagging="false"> ... </application>
这将为应用停用指针标记功能。请注意,这并不会解决底层代码的运行状况问题。这个办法在未来的 Android 版本中将消失,因为这种性质的问题与 MTE 不兼容。