CVE-2022-22063 是某些较旧的高通芯片组中 hypervisor 固件的一个安全问题。未受保护的硬件组件("启动重映射器")可被滥用,从修改后的操作系统获得对 hypervisor 的完全读/写访问权限(权限提升)。在受影响的平台上利用该问题非常简单,因为不需要知道特定固件版本(例如地址或变量)的知识。
注意: 尽管高通已向客户提供了修复(有足够时间发布更新),但许多受影响的设备已经相当老旧,可能无法收到供应商的修复。该问题只能从修改过或已受损的操作系统(利用另一个安全问题)中利用。即使固件存在漏洞,保持操作系统更新和安全也可能足够。
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H该问题也已发布在[高通 2022 年 12 月安全公告]中。
该问题取决于受影响目标上的硬件和软件组合:
hyp 分区内的 ELF 映像)。APCS_BOOT_START_ADDR_NSEC 的硬件寄存器进行配置),该重映射器未受 hypervisor 保护,因此可由权限较低的操作系统内核(例如 Linux)访问。还有几个芯片组可能具有受影响的硬件(例如 MSM8909 和 MSM8953),但它们没有独立的 hypervisor 固件可被攻陷。
权限提升: 给定一个已被入侵的操作系统内核(例如 Linux),该问题允许轻松将权限提升到 hypervisor 级别(ARM 上是 EL1 -> EL2)。可以读取或写入由 hypervisor 管理的所有内存。这打破了由 hypervisor 管理的不同安全域或虚拟机(如果有的话,取决于配置)之间的隔离。
(另请参阅:[Snapdragon 平台上的访问控制简介])
安全启动: 生产环境中可用的多数高通设备都使用安全启动来防止固件的未授权修改。固件由启动链进行加密签名和验证。该问题允许在运行时从已修改的操作系统(通过官方支持的“引导加载程序解锁”或另一个漏洞)修改甚至完全替换已加载的 hypervisor 固件。
(另请参阅:[高通安全启动与镜像认证技术概述 (v1.0)] 和 (v2.0))
注意: 该问题最初是在高通 Snapdragon 410 (MSM8916) 平台上发现的。以下某些解释可能特定于 MSM8916,例如:
然而,总体概念类似地适用于所有受影响平台。
ARMv8-A 64 位架构定义了 4 个特权级别(“异常级别”,EL)。通常用于应用程序、操作系统内核和 hypervisor 的级别是分开的:
CPU 在异常期间(例如由于传入中断)在这些级别之间切换。也可以使用特殊指令(例如 Hypervisor Call (hvc))在部分级别之间切换。
(另请参阅:AArch64 异常模型)
Hypervisor 可以托管一个或多个具有独立操作系统内核的虚拟机。每个虚拟机可以通过第 2 阶段转换获得自身的内存视图。来自虚拟机的所有内存访问都经过两个转换阶段:第一阶段由(虚拟)操作系统管理,而第二阶段由 hypervisor 管理。hypervisor 或其他虚拟机的内存可以通过在转换表中省略来隐藏。
(另请参阅:AArch64 虚拟化,AArch64 内存管理)
高通的 hypervisor 固件运行在 EL2,并使用第 2 阶段转换禁止运行在 EL1 的主操作系统内核(通常是 Linux)访问 hypervisor 内存。注意,在此设置中,第 2 阶段转换主要用于内存保护,而不进行地址转换。主操作系统可以直接访问内存映射输入/输出 (MMIO) 空间中的大多数硬件组件,例如 SD 控制器或相机子系统。访问属于 hypervisor/EL2 (hyp) 和安全监控器/EL3(tz 的一部分)的内存受到限制:
启动重映射器与虚拟化无关:它在 CPU 内核的早期启动期间是必需的。在此硬件平台上,CPU 内核始终从地址 0x0 开始执行。启动重映射器是一个围绕 CPU 构建的额外硬件组件,将前 64 或 128 KiB (0x00000 - 0x20000) 重映射到可配置的内存区域。
默认情况下,启动重映射器指向引导 ROM(设备启动时首先运行的代码)。稍后映射会被更改,以便其他 CPU 内核立即在已加载到 RAM 中的 EL3 固件(tz 的一部分)中开始执行:
注意 CPU 访问的地址(在 tz 内)如何通过两个不同的物理地址访问:RAM 中的真实地址 (0x8650xxxx) 和使用启动重映射器的重映射地址 (0x0000xxxx)。
实际上有两个独立的启动重映射器实例:
APCS_BOOT_START_ADDR_SEC (= 0x0b010004) 进行配置,但仅在安全状态下。APCS_BOOT_START_ADDR_NSEC (= 0x0b010008) 进行配置,即使在非安全状态下。两个启动重映射器实例都可通过一个内存寄存器进行配置,该寄存器包含重映射区域的基本地址和两个配置位:启用重映射的 REMAP_EN 和将前 128 KiB 而不是仅 64 KiB 进行重映射的 BOOT_128KB_EN。
(另请参阅:[高通 Snapdragon 410E 技术参考手册 rev. D],第 85 和 116 页)
利用前两节的知识,基本思想很简单:使用启动重映射器绕过 hypervisor 的内存保护(第 2 阶段转换)。
启动重映射器不仅在 CPU 启动期间有效。它可以在任何时候使用,并允许对重映射区域进行完全读/写/执行访问。此外,高通的 hypervisor 似乎没有阻止操作系统在受影响的设备上配置和访问非安全实例的启动重映射器(它未受第 2 阶段转换保护)。因此,该问题很容易通过以下方式利用:
hyp),然后重映射区域可以动态移动(逐块移动),以访问通过启动重映射器可用的 64/128 KiB 更大的内存区域。也可以使用此方法完全禁用 hypervisor 内存保护(参见概念验证)。
注意: 相同的利用方法不适用于安全世界固件 (tz)。虽然启动重映射器允许绕过第 2 阶段转换,但 DRAM 中的 tz 内存区域似乎受到额外的硬件组件(CPU 外部)的保护,该组件在访问通过启动重映射器后将其阻止:
tz 内存区域可能仅在安全状态下才可访问。该利用方法仅允许绕过 hypervisor 的内存保护,其他硬件安全机制仍然有效。
无需了解固件版本,即可在运行时使用启动重映射器完全禁用并替换原始 hypervisor 固件。特别地,不需要使用逆向工程来获取可能被修改变量和函数的内存地址。只需知道 hypervisor 固件的大致内存区域即可,例如来自开源 Linux 代码中的内存预留,或通过读取 hypervisor 固件二进制文件(在内部存储的 hyp 分区中可用)的 ELF 头。
总体思路是:
hvc),从操作系统切换到 hypervisor(从 EL1 到 EL2)。实现此功能的代码并不长,但涉及一些低级 AArch64 汇编和与 CPU 缓存的仔细交互。然而,主要问题仍然悬而未决:如何在不使代码特定于某个 hypervisor 固件版本的情况下确定 shell 代码应写入的位置?
在 Hypervisor Call(或任何异常通常)期间,CPU 执行被强制到一个特殊的内存地址:异常向量。异常向量是更大的向量表的一部分,该表包含处理来自当前或较低异常级别的不同类型异常的代码:
每个框代表一个异常向量,具有 32 条汇编指令的空间。这空间不够,因此它们通常包含分支指令,跳转到有更多空间用于额外代码的其他地方。
偏移量相对于定义每个异常级别向量表基地址的向量基地址寄存器 (VBAR)。hypervisor 将基地址写入 VBAR_EL2 CPU 寄存器。
Hypervisor Call 是一个从较低异常级别(运行在 EL1 的操作系统内核到 EL2 的 hypervisor)发出的同步异常。如果操作系统内核以 32 位模式运行,CPU 将跳转到 VBAR_EL2+0x600;如果以 64 位模式运行,则跳转到 VBAR_EL2+0x400。在通过启动重映射器将自定义代码写入此地址并发出 Hypervisor Call 后,CPU 将开始执行 shell 代码。
不幸的是,VBAR_EL2 对运行在 EL1 的操作系统内核不可读。它只能由 hypervisor (EL2) 本身或更高级别读取。尽管如此,这个知识使得通过暴力猜测入口地址变得更容易:向量表的基地址必须对齐到其大小的倍数(0x800 = 2 KiB)。这意味着在 128 KiB 区域内只有 64 个可能的位置,在 1 MiB 区域内有 512 个:
红色框显示了在 hypervisor 调用期间 CPU 可能跳转到的所有可能位置。将 shell 代码写入所有这些位置足以保持该方法独立于某个特定的固件版本(实际版本会在一个特定地址拥有向量表)。
这可以进一步改进:启动重映射器允许读写访问,因此可以根据从内存位置读取的现有代码/数据添加一些启发式规则。它应该包含有效的 AArch64 (A64) 指令以及一些重复的填充字节,如 NOP 或分支指令。(每个异常向量的 32 条指令空间通常仅部分使用,因为分支到一个有更多空间的适当函数更容易。)
此存储库中包含的概念验证代码是对适用于 Snapdragon 410 (MSM8916/APQ8016) 平台的高通开源 Little Kernel (LK) 引导加载程序的修改,最初旨在与 DragonBoard 410c 开发板一起测试。选择所有这些是为了简单起见,该问题也可以从其他操作系统(例如 Linux)、其他受影响平台甚至具有安全启动的设备中利用——只要有一种在操作系统内核内执行自定义代码的方法。
该代码实现了上述方法,以完全禁用正在运行的 hypervisor,然后将其替换为不同版本。新的“hypervisor”不支持任何虚拟机,但能够通过一个简单的 hypervisor 调用给出《生命、宇宙及一切之终极问题》的答案:``` $ fastboot oem CVE-2022-22063 < waiting for any device > (bootloader) Hypervisor, what is the Answer to The Ultimate Question of (bootloader) Life, the Universe and Everything? (bootloader) Old hypervisor returned answer: -2 (bootloader) Old non-secure boot remapper base address: 0x100000 (bootloader) Setting boot remapper to hypervisor memory (0x86400000) (bootloader) Using boot remapper to copy shell code to hypervisor memory (bootloader) Copying to all possible vector tables (starting at 0x600) (bootloader) Calling shell code to disable running hypervisor (bootloader) Found old EL2 vector base address at 0x86404000 (bootloader) Copying new code directly to hypervisor memory (0x86400000) (bootloader) Hypervisor, what is the Answer to The Ultimate Question of (bootloader) Life, the Universe and Everything? (bootloader) New hypervisor returned answer: 42 OKAY [ 0.015s] Finished. Total time: 0.015s
### 测试
概念验证代码可以通过以下方式构建和测试:```shell
$ git clone https://git.codelinaro.org/clo/la/kernel/lk.git -b caf_migration/LA.BR.1.2.9.1_rb1.5
$ cp CVE-2022-22063.c lk/platform/msm8916/
$ git apply LK.diff
# Build LK for msm8916 and flash it to device
$ fastboot oem CVE-2022-22063
...
然而,此配置仅适用于 DragonBoard 410c 开发板 以及其他没有安全启动且主引导加载程序可以轻松(且安全)替换的设备。此仓库中的代码主要供参考,并非为了轻松使用/测试。
未来计划将该代码整合到新版本的 lk2nd 中,这样可以更轻松地在各种设备上进行测试,而无需替换主引导加载程序。
据高通称,该问题通过使用第二阶段地址转换禁止访问引导重映射区域得到修复。引导重映射配置(APCS_BOOT_START_ADDR_NSEC 寄存器)仍然可访问,但现在两个内存区域都被屏蔽:
另一种可能的修复方法是在虚拟机监视器中保护 APCS_BOOT_START_ADDR_NSEC 寄存器。在这种情况下,重映射区域仍然可以保持可访问,因为操作系统无法将引导重映射器重新配置到其他内存区域。
据高通称,该问题处理起来特别困难,因为他们无法使用常用的自动化工具。他们收到的大多数问题都是软件问题,可以通过检查源代码来确定受影响的设备。对于该问题,需要手动检查硬件和软件(该平台是否包含有问题的寄存器,以及虚拟机监视器是否存在漏洞)。不幸的是,不使用自动化工具也意味着该问题不会自动排入安全公告日程。第二次保密期延长是为了让客户了解该安全问题,并给他们时间修补设备。他们正在改进流程,以避免未来报告中出现此类问题。
CVE-2022-22063 报告与示意图 © 2022 由 Stephan Gerhold 以 知识共享 署名-相同方式共享 4.0 国际许可协议 (CC BY-SA 4.0) 授权。
概念验证代码(CVE-2022-22063.c)以 MIT 许可证提供。