首款搭载MTE的手机上市
作者:Mark Brand,Google Project Zero
引言
终于到了我兑现长期承诺的时候了。自从我第一次听说ARM的内存标记扩展(Memory Tagging Extensions)以来,我就说过(对太多人说过,现在已经无法反悔了……)我会立即切换到第一款支持此功能的可用设备。等待是漫长的(自2017年底以来),但随着新款 Pixel 8 / Pixel 8 Pro 手机的发布,终于有了一款可以启用MTE的量产手机!
MTE能够在首次危险访问时检测到内存损坏利用,这在诊断和潜在安全有效性方面是一个重大改进。MTE首次在量产手机上可用是一个巨大的进步,我认为这项技术有真正的潜力让0-day漏洞利用变得更困难。
自发布之日起,我就一直在我的Pixel 8上启用MTE运行,到目前为止,我还没有发现我日常使用的任何应用程序1有任何问题,也没有任何明显的性能问题。
目前,MTE在Pixel上仅作为开发者选项提供,旨在让应用程序开发者使用MTE测试他们的应用程序,但我们可以将其配置为对所有2应用程序和原生用户模式二进制文件默认启用同步模式。这可以在官方系统映像上完成,无需解锁引导加载程序或获取root权限——只需几条调试器命令。我们现在就来做这个,但首先:
免责声明
这绝对不是受支持的设备配置;如果你以这种方式设置设备,很可能会遇到至少某些应用程序崩溃或无法正常运行的问题。
这就是我配置个人Pixel 8的方式,到目前为止我还没有遇到任何问题,但这对我来说有些意外,我仍在等待看到第一个完全无法运行的应用程序会是什么……
在Pixel 8/Pixel 8 Pro上启用MTE
在Android设备上启用MTE需要引导加载程序为存储标签预留一部分设备内存。这意味着有两个独立的地方需要启用MTE——首先我们需要配置引导加载程序来启用它,然后我们需要配置系统在应用程序中使用它。
首先,我们需要按照 Android说明 在设备上启用开发者模式和USB调试:

现在我们需要将手机连接到一台安装了 Android调试工具 的可信计算机——我使用的是我的Linux工作站:
markbrand@markbrand$ adb devices -l
设备列表
XXXXXXXXXXXXXX 设备 usb:3-3 product:shiba model:Pixel_8 device:shiba transport_id:5
markbrand@markbrand$ adb shell
shiba:/ $ setprop arm64.memtag.bootctl memtag
shiba:/ $ setprop persist.arm64.memtag.default sync
shiba:/ $ setprop persist.arm64.memtag.app_default sync
shiba:/ $ reboot
这些命令做了几件事——首先,我们配置引导加载程序在启动时启用MTE。第二条命令为设备上运行的原生可执行文件设置默认的MTE模式,第三条命令为应用程序设置默认的MTE模式。应用程序开发者可以通过使用 manifest 来启用MTE,但这个系统属性设置了应用程序的默认MTE模式,有效地使其成为选择退出(opt-out)而非选择加入(opt-in)。
在讨论应用程序选择退出的话题时,值得注意的是,Chrome 对大多数分配不使用系统分配器,而是使用 PartitionAlloc。目前正在开发实验性的MTE支持,可以通过一些额外的步骤3启用。不幸的是,目前这需要设置一个命令行标志,这涉及一些安全权衡。我们期望Chrome在不久的将来会添加一种更简单的方法来启用MTE支持,而不会出现这些问题。
如果我们查看所有的系统属性,可以看到有一些与内存标记相关的附加属性:
shiba:/ $ getprop | grep memtag
[arm64.memtag.bootctl]: [memtag]
[persist.arm64.memtag.app.com.android.nfc]: [off]
[persist.arm64.memtag.app.com.android.se]: [off]
[persist.arm64.memtag.app.com.google.android.bluetooth]: [off]
[persist.arm64.memtag.app_default]: [sync]
[persist.arm64.memtag.default]: [sync]
[persist.arm64.memtag.system_server]: [off]
[ro.arm64.memtag.bootctl_supported]: [1]
不幸的是,有一些默认的排除项我们无法覆盖——系统属性的保护意味着我们目前无法在正常的生产版本中为少数组件启用MTE——这些例外是 system_server 以及与nfc、安全元件(secure element)和蓝牙相关的应用程序。
我们想确认这些命令是否有效,所以我们现在就检查一下。首先检查它是否对原生可执行文件有效:
shiba:/ $ cat /proc/self/smaps | grep mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
VmFlags: rd wr mr mw me ac mt
765bff1000-765c011000 r--s 00000000 00:12 97 /dev/properties/u:object_r:arm64_memtag_prop:s0
我们可以看到我们的 cat 进程的映射设置了 mt 位,因此该进程已启用MTE。
现在为了检查没有任何manifest设置的应用程序是否也启用了MTE,我们在一个空的JNI项目中添加了一小段代码来触发一个释放后使用(use-after-free)漏洞:
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_mtetestapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
char* ptr = strdup("test string");
free(ptr);
// 下面的ptr访问会导致释放后使用。
return env->NewStringUTF(ptr);
}
如果没有MTE,应用程序运行这段代码很可能不会崩溃。我还确保应用程序manifest没有设置MTE,因此它将继承默认设置。当我们启动应用程序时,我们将看到它是否会崩溃,以及崩溃是否由MTE检查失败引起!

查看logcat输出,我们可以看到崩溃的原因是同步MTE标签检查失败(SEGV_MTESERR)。
DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
DEBUG : Build fingerprint: 'google/shiba/shiba:14/UD1A.230803.041/10808477:user/release-keys'
DEBUG : Revision: 'MP1.0'
DEBUG : ABI: 'arm64'
DEBUG : Timestamp: 2023-10-24 16:56:32.092532886+0200
DEBUG : Process uptime: 2s
DEBUG : Cmdline: com.example.mtetestapplication
DEBUG : pid: 24147, tid: 24147, name: testapplication >>> com.example.mtetestapplication <<<
DEBUG : uid: 10292
DEBUG : tagged_addr_ctrl: 000000000007fff3 (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)
DEBUG : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
DEBUG : signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0b000072afa9f790
DEBUG : x0 0000000000000001 x1 0000007fe384c2e0 x2 0000000000000075 x3 00000072aae969ac
DEBUG : x4 0000007fe384c308 x5 0000000000000004 x6 7274732074736574 x7 00676e6972747320
DEBUG : x8 0000000000000020 x9 00000072ab1867e0 x10 000000000000050c x11 00000072aaed0af4
DEBUG : x12 00000072aaed0ca8 x13 31106e3dee7fb177 x14 ffffffffffffffff x15 00000000ebad6a89
DEBUG : x16 0000000000000001 x17 000000722ff047b8 x18 00000075740fe000 x19 0000007fe384c2d0
DEBUG : x20 0000007fe384c308 x21 00000072aae969ac x22 0000007fe384c2e0 x23 070000741fa897b0
DEBUG : x24 0b000072afa9f790 x25 00000072aaed0c18 x26 0000000000000001 x27 000000754a5fae40
DEBUG : x28 0000007573c00000 x29 0000007fe384c260
DEBUG : lr 00000072ab35e7ac sp 0000007fe384be30 pc 00000072ab1867ec pst 0000000080001000
DEBUG : 98 total frames
DEBUG : backtrace:
DEBUG : #00 pc 00000000003867ec /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*) (.__uniq.99033978352804627313491551960229047428)+1636) (BuildId: a5fcf27f4a71b07dff05c648ad58e3cd)
DEBUG : #01 pc 000000000055e7a8 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*) (.__uniq.99033978352804627313491551960229047428.llvm.6178811259984417487)+160) (BuildId: a5fcf27f4a71b07dff05c648ad58e3cd)
DEBUG : #02 pc 00000000000017dc /data/app/~~lgGoAt3gB6oojf3IWXi-KQ==/com.example.mtetestapplication-k4Yl4oMx9PEbfuvTEkjqFg==/base.apk!libmtetestapplication.so (offset 0x1000) (_JNIEnv::NewStringUTF(char const*)+36) (BuildId: f60a9970a8a46ff7949a5c8e41d0ece51e47d82c)
...
DEBUG : Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.
DEBUG : Cause: [MTE]: Use After Free, 0 bytes into a 12-byte allocation at 0x72afa9f790
DEBUG : deallocated by thread 24147:
DEBUG : #00 pc 000000000005e800 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+496) (BuildId: a017f07431ff6692304a0cae225962fb)
DEBUG : #01 pc 0000000000057ba4 /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: a017f07431ff6692304a0cae225962fb)
DEBUG : #02 pc 000000000000179c /data/app/~~lgGoAt3gB6oojf3IWXi-KQ==/com.example.mtetestapplication-k4Yl4oMx9PEbfuvTEkjqFg==/base.apk!libmtetestapplication.so (offset 0x1000) (Java_com_example_mtetestapplication_MainActivity_stringFromJNI+40) (BuildId: f60a9970a8a46ff7949a5c8e41d0ece51e47d82c)
如果你只想检查引导加载程序中是否已启用MTE,Google动态工具团队在Play商店上有一个 应用程序,你也可以使用(这个应用程序在manifest中以异步模式启用MTE,这就是为什么你在下面看到它并非在所有核心上都以同步模式运行):

此时,我们可以回到开发者设置并禁用USB调试,因为我们不希望在日常使用中启用它。我们需要保持开发者模式开关开启,因为禁用它将在下次重启时完全关闭MTE。
结论
启用同步MTE的Pixel 8至少主观上在性能和电池续航方面比我之前的手机有所提升。
我认为这对设备的整体安全性是一个巨大的改进——许多零点击攻击面涉及大量不安全的C/C++代码,无论是用于通话的WebRTC,还是众多媒体或图像文件解析库之一。MTE 并非解决内存安全问题的万能药——但第一款能够以同步MTE运行几乎所有用户模式应用程序的量产设备的发布是一个巨大的进步,值得庆祝!
1 在一位团队成员的设备上,上周发生了一次MTE检测到释放后使用漏洞的情况。这导致了一次在当时未被注意到的崩溃,但后来我们在查看其设备上保存的崩溃报告时发现了它。由于记录了分配的分配和释放堆栈跟踪,我们能够快速找出漏洞并报告给应用程序开发者——在这种情况下,漏洞是由用户手势输入引起的,并没有真正的安全影响,但它已经说明了MTE的一些优势。
2 除了安全元件(secure element)、蓝牙、nfc和系统服务器(system server),因为这些系统应用程序在Pixel系统映像中明确将其各自的系统属性设置为'off'。
3 在Chrome中启用MTE需要设置多个命令行标志,在未root的Android设备上,这需要配置Chrome从 /data/local/tmp 中的文件加载命令行标志。这可能不安全,因此我们不建议这样做,但如果你想在测试设备上进行实验或用于fuzzing,以下命令将允许你启用MTE运行Chrome:
markbrand@markbrand:~$ adb shell
shiba:/ $ umask 022shiba:/ $ echo "_ --enable-features=PartitionAllocMemoryTagging:enabled-processes/all-processes/memtag-mode/sync --disable-features=PartitionAllocPermissiveMte,KillPartitionAllocMemoryTagging" > /data/local/tmp/chrome-command-lineshiba:/ $ ls -la /data/local/tmp/chrome-command-line
-rw-r--r-- 1 shell shell 176 2023-10-25 19:14 /data/local/tmp/chrome-command-line