TrustZone 插曲:i.MX 8M 上 OP-TEE 内存隔离缺陷
摘要
文章揭示,由于缺少 TZASC 配置,NXP i.MX 8M SoC 上的 OP-TEE 内存隔离可以从普通世界被绕过,从而允许拥有内核访问权限的攻击者读取安全世界内存。修复方案已在上游 OP-TEE v4.10.0+ 中提供,需配置 CFG_TZASC_REGION0_SECURE=y,或使用下游版本 lf-6.12.49_2.2.0。
<p><a href="https://lobste.rs/s/uz1fle/trustzone_intermezzo_broken_op_tee">评论</a></p>
查看缓存全文
缓存时间:
2026/06/11 13:39
# TrustZone Intermezzo: i.MX 8M 上 OP-TEE 内存隔离机制存在漏洞
来源:https://sigma-star.at/blog/2026/06/trustzone-intermezzo/
**TL;DR:** 如果你运行的 OP-TEE 不是上游 v4.10.0 以上版本(且未启用 `CFG_TZASC_REGION0_SECURE=y`),或者 i.MX 下游 OP-TEE 不是 lf-6.12.49_2.2.0 以上版本,那么 OP-TEE 的内存就未得到正确保护,可能被普通世界(normal world)攻破。
最近,OP-TEE 操作系统 git 仓库中一个 2024 年的提交(https://github.com/OP-TEE/optee_os/commit/31bb491f8e7c78794b6f9615cc4f2deec58b4ed9)引起了我们的注意。该提交确保在所有 i.MX 8M SoC 上启用 TrustZone 地址空间控制器(TZASC)。我们的第一反应是复杂的。难道在此之前 OP-TEE 真的“忘记”保护自己的内存区域了?还是说这个提交只是针对某些错误配置增加了另一层防护?如果是后者,为什么没有提交 CVE?
回想一下两个世界是如何划分的。OP-TEE 运行在 SoC 所谓的“安全世界”(secure world)中,而像 Linux 这样的操作系统则运行在“普通世界”(non-secure world)中。Arm TrustZone 通过 TZASC 强制实现这种划分。
因此,普通世界绝对不能直接访问安全世界的内存。OP-TEE 通常实现软件 TPM 或持有其他密钥。Linux 不能触碰它,无论是内核还是用户空间的 root 都不行。
这个提交一直困扰着我们,所以我们决定深入挖掘并尝试一下。我们的许多客户都在使用基于 i.MX 8M 的系统,并且非常重视安全性。我们在过程中获得了不少乐趣,所以这篇博客记录了我们的探索历程。
## 确认漏洞
我们启动了一块 i.MX 8M 开发板,准确的说是 NXP i.MX 8M Mini EVK,运行的是 OP-TEE 4.1.0(不含那个提交)。在这个系统上,OP-TEE 将其主内存放在地址 `0xbe000000`,所以我们尝试从 Linux 转储该区域。这个尝试本应失败,因为 Linux 运行在普通世界,而 OP-TEE 运行在安全世界。
在 Linux 上,root 可以通过 `/dev/mem` 访问所有系统内存,因此我们尝试通过这种方式读取 `0xbe000000`。虽然有 devmem2 (https://github.com/denix0/devmem2) 这样的工具,但我们写了几行 Python 以保持灵活性。访问立即失败了。很好。对吧?
别急着下结论。这个系统上的内核是用 `CONFIG_STRICT_DEVMEM` 编译的。该选项将 `/dev/mem` 限制为只能访问设备内存,所以它在 TrustZone 发挥作用之前就阻止了我们的读取。是时候在内核禁用 `CONFIG_STRICT_DEVMEM` 的情况下重新编译了。在新编译的内核上,读取仍然失败。很好,但我们确定吗?再深入一想,我们查看了内核日志,发现了这一行:
```
OF: reserved mem: 0x00000000be000000..0x00000000bfbfffff (28672 KiB) nomap non-reusable optee_core@be000000
```
OP-TEE 会向传递给 Linux 的设备树添加保留内存节点,所以 Linux 完全忽略了这个内存区域。特别是 `nomap` 属性确保了该区域永远不会被映射。因此我们的测试无法进行:Linux *自身*将 OP-TEE 区域挡在了门外。但这并不是我们想要测试的。我们想知道的是访问是否*可能*。Linux 运行在普通世界,即使它真的想访问那块内存,也绝对不能成功。
修改了几行代码后,我们有了一个忽略 OP-TEE 的 `nomap` 提示的内核。在现实场景中,攻击者控制了内核之后也能绕过这个限制,最简单的方法就是加载一个自定义内核模块。是时候再次测试了。
```
# 转储内存
$ python3 devmem.py 0xbe000000 0x100000 > optee_core.bin
# 检查是否有已知的字符串
$ strings optee_core.bin | grep "OP-TEE version"
OP-TEE version: %s
```
确实,我们可以从 Linux 读取 OP-TEE 的内存!这个漏洞是真实且严重的。它使 OP-TEE 的整个安全目的化为乌有。被攻破的 Linux 系统可以访问安全世界,提取甚至修改关键材料。
## 还没结束:神秘的 region0 和内存别名
这个故事本可以到此为止。OP-TEE 忘了在某个平台上强制执行内存限制,一个提交修复了它,然后我们就没事了。虽然不好,但 bug 难免。这就是生活。
进一步调查时,我们发现了其他与 TZASC 相关的提交:
- arm-trusted-firmware: fix(imx8m): don't reconfigure default region0 (https://github.com/ARM-software/arm-trusted-firmware/commit/9bf148071aad597e7fe7d1080c00aeb35b67a3dd)
- optee: drivers: imx: tzc380: add support to verify region0 (https://github.com/OP-TEE/optee_os/commit/443c5817de47f1bd19091b419806898070382a67)
这些提交信息暗示着一个更深层次的问题。保护安全世界免受普通世界攻击,不仅仅是正确启用 TZASC 那么简单。根本原因在于 TZASC 无法感知内存别名。通过正确的修复 (https://github.com/OP-TEE/optee_os/commit/31bb491f8e7c78794b6f9615cc4f2deec58b4ed9),OP-TEE 确保了其内存区域无法从普通世界访问。但这种保护仅依赖于地址本身(在我们的例子中是 `0xbe000000`)。由于控制器无法感知内存别名,另一个地址可能指向完全相同的内存位置,从而绕过访问检查。
这就是 `region0` 变得相关的地方。TZASC 允许为某些内存地址范围配置访问权限。如果请求的地址在 TZASC 中没有配置范围,则会回退到 `region0`。`region0` 是一个覆盖 SoC 已知整个地址空间的内存范围配置。可以把它看作通配符。在复位状态,`region0` 是安全只读的,确保了回退的安全性。那么一切都好吗?遗憾的是并非如此,而这正是 TF-A 提交 (https://github.com/ARM-software/arm-trusted-firmware/commit/9bf148071aad597e7fe7d1080c00aeb35b67a3dd) 修复的问题。如果在你的系统上存在内存别名,并且在引导过程中某个组件重新配置了 `region0` 以允许普通世界访问,那么你就陷入大麻烦了。从配置上看不出任何危险。
## 继续深入
升级了 OP-TEE 和 TF-A 之后,一切似乎都正常了。访问 `0xbe000000` 已经不再可能。一切都好,直到我们注意到测试设置中 OP-TEE 配置选项 `CFG_TZASC_REGION0_SECURE` 是禁用的。`CFG_TZASC_REGION0_SECURE` 会测试 `region0` 是否配置为安全模式(即只允许从安全世界访问)。启用它后,残酷的真相就暴露了。OP-TEE 在启动很早的时候就 panic 了:
```
E/TC:0 0 Panic 'region0 is not secure configured, non-secure memory alias access possible!' at core/arch/arm/plat-imx/tzc380.c:217
```
所以我们系统上*仍然*有东西在触碰 `region0`。顺着线索,我们查看了引导加载程序 U-Boot (https://source.denx.de/u-boot/) 做了什么。在 `arch/arm/mach-imx/imx8m/soc.c` 中,它允许对 `region0` 的非安全访问。
```
void enable_tzc380(void)
{
[...]
/*
* set Region 0 attribute to allow secure and non-secure
* read/write permission. Found some masters like usb dwc3
* controllers can't work with secure memory.
*/
writel(0xf0000000, TZASC_BASE_ADDR + 0x108);
}
```
移除那个 `writel()` 后,OP-TEE 的 panic 消失了,并确认没有其他组件再触碰 `region0`。尽管如此,别名的疑问一直困扰着我们。这是否真的是一个实际问题,还是纯理论问题?i.MX 8M Mini 参考手册显示,在我们的 OP-TEE 使用的区域没有内存别名。也许即使没有 `region0` 的修复,我们也是安全的?因此,我们编写了一个小程序,使用 `/dev/mem` 和 `/proc/self/pagemap` 来找到映射到同一物理内存的地址。它选择一个页面,读取其内容,翻转地址的几个高位,然后检查新地址是否返回相同的字节。结果这个方法远比需要的复杂,因为它找到了第一个别名。
在禁用保护的情况下测试 OP-TEE 区域时,`0xbe000000` 的一个别名是 `0x1be000000`。所以 i.MX 8M 系列的 DDR 控制器会*忽略*内存地址的高 32 位。TZASC 和 DDR 控制器因此产生分歧:TZASC 看到两个无关的地址,而 DDR 控制器将它们映射到同一位置。TZASC 设置不适用于别名,因此访问会落到 `region0`。并非所有高位在 Linux 上都是可用的,因为地址仍然必须适合配置的地址空间。但设置第 32 位似乎工作正常。
作为最终测试,我们重新应用了所有修复,但保留了 U-Boot 不变(这样它还会重新配置 `region0`),并尝试转储 OP-TEE 的内存:
```
$ python3 devmem.py 0xbe000000 0x100000 > optee_core.bin
Bus error
$ python3 devmem.py 0x1be000000 0x100000 > optee_core.bin
$ strings optee_core.bin | grep "OP-TEE version"
OP-TEE version: %s
```
成功了。直接地址被阻止了,但别名畅通无阻,所有保护都白费了。
## 我受影响了吗?
如果你在 i.MX 8M SoC 上运行 OP-TEE,你可能会受到影响。绕过方法由两个独立的问题组成,其中任意一个都足以破坏隔离:
1. 在 OP-TEE 4.2.0 之前,i.MX 8M 上默认不启用 TZASC,因此安全区域从未受到保护。
2. 其他组件(如引导加载程序和 TF-A)重新配置了 `region0`。一旦内存别名落到开放的 `region0`,普通世界就能访问安全内存。
从 OP-TEE 4.10.0 开始,可以检测到这一点。要检查你自己的系统,请运行启用了 `CFG_TZASC_REGION0_SECURE` 的最新 OP-TEE。使用该选项,OP-TEE 会在启动时验证 `region0`,并在其不安全时 panic,因此也能捕获其他组件在背后重新配置 `region0` 的情况。该选项仅在 OP-TEE 4.10.0 及更新版本中可用。
如果你使用 U-Boot 作为引导加载程序,请暂时还原提交 imx8m: Configure trustzone region 0 for non-secure access (https://source.denx.de/u-boot/u-boot/-/commit/b3cf0a8f03d162e030cde1131751d060853e16fc)。我们已向开发人员发送了提醒 (https://lore.kernel.org/u-boot/3208216.Ym5mLc6kNg@nailgun/T/#u)。一旦有更好的解决方案,我们会在本文中更新。
对于 i.MX 下游 OP-TEE 有一个特殊情况。如果你运行它,请至少使用包含提交 LFOPTEE-468 core: plat-imx: tzc380: update TZASC configuration (https://github.com/nxp-imx/imx-optee-os/commit/c09d6e9da171f8c5ee42b42ff144b320761a5f16) 的版本。遗憾的是,下游选择了不同的路径。他们没有坚持修复其他组件,而是重新配置 `region0` 以禁用非安全访问。这只掩盖了真正的问题:其他软件组件出于错误的原因修改了 `region0`。
我们在 U-Boot 邮件列表中的提示在 OP-TEE GitHub 上引发了一场关于如何处理的热烈讨论 (https://github.com/OP-TEE/optee_os/pull/7838)。我们的主要建议很简单:定期更新 OP-TEE、TF-A 和你的引导加载程序,因为它们包含了重要的 bug 修复。遗憾的是,这些问题没有提交 CVE 编号,因此很多用户很可能错过了它们。虽然我们来得有点晚,但已经申请了 CVE 编号,一旦分配,我们将更新这篇博客。
## 结论
现代计算机系统极其复杂,即使成熟的组件也可能隐藏奇怪的问题。我们必须层层剥离才能完全确认真正的问题:
- `CONFIG_STRICT_DEVMEM` 阻止了第一次测试。
- 设备树的 `nomap` 属性使 Linux 根本无法映射该区域。
- 只有在移除这两者之后,TZASC 的别名绕过才变得可见。
从安全和代码审计的角度来看,一个原则再次得到验证:不要相信任何东西。即使是 OP-TEE 的内存保护也需要严格的测试,而且正如我们所示,你必须确保你测试的是正确的东西。一个经验较少的工程师可能会得出一切正常的结论,因为 Linux 自己拒绝了最初的访问尝试。
最后,感谢 Pengutronix (https://www.pengutronix.de/) 解决 TF-A 和 OP-TEE 中的这个问题!
相似文章
Lobsters Hottest
Qualys 披露了 Linux 内核 __ptrace_may_access() 函数中的一个逻辑漏洞 (CVE-2026-46333),可导致本地权限提升和信息泄露。该漏洞自 2016 年起存在,影响多个发行版,Qualys 已开发出四种概念验证利用代码。
Lobsters Hottest
开发者在 Blue Pill 上构建半虚拟机/管理程序的实验笔记,使用 MPU 和 SVC 提供隔离和特权访问控制,无需 MMU 或 TrustZone。
Hacker News Top
AMD通过新版AGESA固件悄悄移除了消费级Ryzen CPU的透明安全内存加密(TSME),可能导致用户易受物理攻击,而AMD工程师对此事保持沉默。
Lobsters Hottest
本文详细介绍了在网络调度子系统(red调度器)中发现并利用的一个Linux内核零日漏洞,将一个受限的slab释放后使用(UAF)转化为完全的物理内存读写,最终实现root权限提升。该漏洞存在了2.5年,于2026年6月被修复。
Lobsters Hottest
FreeBSD中存在一个严重的本地权限提升漏洞(CVE-2026-45257),允许无特权用户将任意数据写入任何可读文件的页面缓存,绕过文件权限和标志,最终导致完全获取root权限。该漏洞影响FreeBSD 13.0及更高版本的默认安装,通过sendfile、KTLS和内核内AES-GCM解密的不安全组合实现。