NX 位:不只是安全那么简单
The NX bit is not just about security
在开发 postmarketOS 的 ARM64 裸机 hypervisor 时,我遇到了一个诡异的故障:一旦启用 CTR_EL0 拦截,手机就会随机死机。排查过程排除了寄存器损坏和硬件越界,最终锁定在一个看似无害的数组排序操作上。由于 ARM 架构的 Icache 与 Dcache 不连贯,动态修改代码段导致了指令缓存失效。更深层的挖掘揭示了一个微架构层面的陷阱:动态跳转指令 blr 触发了分支预测,导致 CPU 推测性地访问了未映射的物理地址,进而引发系统崩溃。这个案例生动地展示了 NX 位(不可执行位)在防止推测执行攻击之外的深层意义,以及 ARM 硬件在实现细节上那些不为人知的微妙之处。
动态分发采用分支预测,而静态分支则预先知晓目标地址,因此不会发生误预测。
HN 评论区
48- repiret
"Speculative instruction fetch to device memory crashes device"(推测性指令获取导致设备内存崩溃)是我有史以来发现的最喜欢的 Bug 之一。我在没有 MPU 的 Cortex-M4 上就遇到过这个问题,所以我甚至无法修复内存属性,唯一的解决办法就是调整编译后的代码,以防止推测性获取。
Armv8/9-A 竟然允许对设备内存进行推测性指令获取,这简直疯了。疯到我都不得不查资料才敢相信:
"硬件不会阻止对具有任何 Device 内存属性的内存位置进行推测性指令获取,除非该内存位置也被标记为所有异常级别下的 execute-never(不可执行)。" —— ARM DDI 0487K.a § B2.15.2
Armv7-M 规范则没那么明确。它确实说过:"架构不允许对标记为 Device 的内存进行推测性访问",这与 Armv8/9-A 形成对比,后者在类似的陈述中限定了是"数据访问"。但随后它又说:"为了确保正确性,读取敏感的位置必须标记为不可执行。"(这些都出自 ARM DDI 0403E.e § A3.5.7)
我手头没有 v7-A 的旧版文档来做对比。
- asveikau
顺着"这不仅仅是安全问题"这个主题:如果你有一个像 use after free 这样的 Bug,并且它恰好覆盖了一个函数指针,那么 nx 位可以确保当你通过调用跟随该指针时,你能在尽可能接近故障点的地方获得一个干净的异常捕获。如果它盲目地将陈旧字节作为代码执行,那么崩溃和堆栈跟踪可能就不会那么清晰了。
不过话说回来,这类正确性 Bug 往往也是安全问题。
- anyfoo
如今,将任何东西映射到 0 地址都被认为是不好的做法……现在你知道原因之一了!
在我所知的具有 1:1 地址空间身份映射的情况下,物理地址空间在 0 处实际上也没有任何东西,所以它仍然不需要被映射。
- mubbicles
我很欣赏这篇文章。我曾问过我们的 CSP 专家两者的区别,但这让我更好地理解 device 和 ns 之间的差异。我也很感激这篇文章看起来不是 AI 写的。
- dmitrygr
这有两点很奇怪,因为实际上故意从设备内存执行代码是不允许的:"尝试从标记为 Device 的区域执行代码是 UNPREDICTABLE(不可预测的)。"所以:无法可靠地从那里执行,但却可以从那里进行推测性指令获取。真是有趣!
https://support.arm.com/documentation/100941/0101/Memory-typ...