CVE-2022-22063 is a security issue in the hypervisor firmware of some older Qualcomm chipsets. An unprotected hardware component (the "boot remapper") can be abused to gain full read/write access to the hypervisor from a modified operating system (privilege escalation). Exploiting the issue is trivial on affected platforms, since knowledge about the specific firmware version (e.g. addresses or variables) is not required.
Note: Although Qualcomm has provided fixes to customers (with plenty time to release updates) many affected devices are already quite old and may not receive the fix from the vendor. The issue can only be exploited from a modified or compromised operating system (using another security issue). Keeping the operating system up-to-date and secure might be sufficient even if the firmware is vulnerable.
- CVE Identifier: CVE-2022-22063
- Security Rating (Qualcomm): Critical
- Common Vulnerability Scoring System: 8.4 (High),
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Common Weakness Enumeration: CWE-1257: Improper Access Control Applied to Mirrored or Aliased Memory Regions and/or CWE-1262: Improper Access Control for Register Interface, Qualcomm categorizes the issue broadly as CWE-16: Configuration.
The issue was also published in Qualcomm's December 2022 Security Bulletin.
The issue depends on a combination of hardware and software running on the affected target:
- Software: The device runs a separate Qualcomm-provided "hypervisor" firmware (usually an ELF image in the
hyp
partition on internal storage). - Hardware: There is a non-secure version of the boot remapper (usually configurable using a hardware register called
APCS_BOOT_START_ADDR_NSEC
), which is not protected by the hypervisor and is therefore accessible by the less privileged operating system kernel (e.g. Linux).
There are several more chipsets which likely have the affected hardware (e.g. MSM8909 and MSM8953), but they do not have a separate hypervisor firmware that could be compromised.
-
Privilege Escalation: Given an already compromised operating system kernel (e.g. Linux), the issue allows trivially elevating privileges to the hypervisor level (EL1 -> EL2 on ARM). All memory managed by the hypervisor can be read or written. This breaks the isolation of different security domains or virtual machines managed by the hypervisor (if any, depending on the configuration).
(See also: An introduction to Access Control on Qualcomm Snapdragon Platforms) -
Secure Boot: Most Qualcomm devices available in production make use of secure boot to prevent unauthorized modification of the firmware. The firmware is cryptographically signed and verified by the boot chain. The issue allows modifying or even fully replacing the loaded hypervisor firmware at runtime from a modified operating system (either through officially supported "bootloader unlocking" or another exploit).
(See also: Qualcomm Secure Boot and Image Authentication Technical Overview (v1.0) and (v2.0))
Note: The issue was originally found on the Qualcomm Snapdragon 410 (MSM8916) platform. Some of the following explanations might be specific to MSM8916, e.g.:
- Specific memory addresses
- 64-bit ARM/AArch64 firmware design (some affected platforms support 32-bit ARM/AArch32 only)
However, the general concept applies similarly to all affected platforms.
The ARMv8-A 64-bit architecture defines 4 privilege levels ("exception levels", EL). There are separate levels that are typically used for applications, operating system kernels and a hypervisor:
The CPU switches between the levels during exceptions, e.g. because of an incoming interrupt. It is also possible to switch between some of the levels using special instructions, such as the Hypervisor Call (hvc
).
(See also: AArch64 Exception model)
The hypervisor can host one or multiple virtual machines with separate operating system kernels. Each virtual machine can be given its own view of memory using stage 2 translation. All memory accesses from a virtual machine pass through two translation stages: the first is managed by the (virtual) operating system, while the second stage is managed by the hypervisor. Memory used by the hypervisor or other virtual machines can be hidden by omitting it from the translation tables.
(See also: AArch64 virtualization, AArch64 memory management)
Qualcomm's hypervisor firmware runs in EL2 and uses stage 2 translation to disallow access to the hypervisor memory from the main operating system kernel running in EL1 (usually Linux). Note that in this setup the stage 2 translation is primarily used for memory protection, without address translation. The main operating system gets direct access to most hardware components in the memory-mapped input/output (MMIO) space, e.g. the SD controller or the camera subsystem. Access to memory that belongs to the hypervisor/EL2 (hyp
) and secure monitor/EL3 (part of tz
) is restricted:
The boot remapper is not related to virtualization: It is needed during early boot of a CPU core. On this hardware platform the CPU cores always start execution at address 0x0. The boot remapper is an extra hardware component built around the CPU that remaps the first 64 or 128 KiB (0x00000 - 0x20000) to a configurable memory region.
By default the boot remapper points to the boot ROM (the first code that runs when the device is started). Later the mapping is changed so that the other CPU cores immediately start execution in the EL3 firmware (part of tz
) that was loaded into the RAM:
Note how the address accessed by the CPU (within tz
) is accessible using two different physical addresses: the real address in RAM (0x8650xxxx) and the remapped address using the boot remapper (0x0000xxxx).
There are actually two separate instances of the boot remapper:
- Secure: Remaps memory accesses made in secure state. This is the instance used for CPU start-up, since the CPU initially begins execution in secure state (EL3). It is configurable using
APCS_BOOT_START_ADDR_SEC
(= 0x0b010004) but only in secure state. - Non-secure: Remaps memory accesses made in non-secure state. It is configurable using
APCS_BOOT_START_ADDR_NSEC
(= 0x0b010008), even in non-secure state.
Both boot remapper instances can be configured using a memory register that contains the base address for the remapped region and two configuration bits: REMAP_EN
to enable remapping and BOOT_128KB_EN
to remap the first 128 KiB instead of just 64 KiB.
(See also: Qualcomm Snapdragon 410E Technical Reference Manual rev. D, page 85 and 116)
Using the knowledge of the previous two sections the basic idea is simple: Use the boot remapper to bypass the memory protection (stage 2 translation) of the hypervisor.
The boot remapper does not only work during CPU start-up. It can be used anytime and allows full read/write/execute access to the remapped region. Also, Qualcomm's hypervisor does not seem to prevent the operating system from configuring and accessing the non-secure instance of the boot remapper on affected devices (it is not protected using the stage 2 translation). Therefore, the issue is easy to exploit by:
- Configuring the boot-remapper to point to an arbitrary memory region that is normally protected by the stage 2 translation (e.g. the hypervisor firmware
hyp
starting at address 0x8640xxxx), followed by - Reading/writing through the boot remapper (address 0x0000xxxx).
The remapped region can be shifted dynamically (block by block) to access memory regions larger than the 64/128 KiB available through the boot remapper. It is also possible to use this to disable the hypervisor memory protection entirely (see Proof of Concept).
Note: The same exploit does not work for the secure world firmware (tz
). While the boot remapper allows bypassing the stage 2 translation, the tz
memory region in DRAM seems to be protected by an additional hardware component (outside of the CPU) that blocks the access after it has passed through the boot remapper:
The tz
memory region is likely only accessible in secure state. The exploit only allows bypassing the memory protection of the hypervisor, other security mechanisms of the hardware are still in place.
The boot remapper can be used to fully disable and replace the original hypervisor firmware at runtime, without knowledge about the firmware version. In particular, it is not necessary to use reverse engineering to obtain memory addresses of variables and functions that could be modified. It is enough to know the rough memory region of the hypervisor firmware, e.g. from the memory reservation in the open-source Linux code or by reading the ELF headers of the hypervisor firmware binary (available in the hyp
partition on internal storage).
The general idea is:
- Use the boot remapper to overwrite the code used to handle Hypervisor Calls from the operating system.
- Make a Hypervisor Call (
hvc
) to switch from the operating system into the hypervisor (from EL1 to EL2). - Let the shell code fully disable the hypervisor, including the stage 2 translation used for memory protection. Then return back to EL1.
- The hypervisor memory is now accessible directly from EL1, without going through the boot remapper.
The code that implements this is not long, but involves some low-level AArch64 assembly and careful interaction with the CPU caches. However, the main question is still open: Where exactly should the shell code be written, without making the code specific to one particular hypervisor firmware version?
During a Hypervisor Call (or any exception in general) CPU execution is forced to a special memory address: the exception vector. The exception vectors are part of a larger vector table that contains the code that handles different types of exceptions, coming from the current or lower exception levels:
Each box represents an exception vector with space for 32 assembly instructions. This is not enough space so they usually contain branch instructions that jump somewhere else with more space for additional code.
The offsets are relative to the Vector Base Address Register (VBAR) that defines the base address of the vector table for each exception level. The hypervisor writes the base address to the VBAR_EL2
CPU register.
A Hypervisor Call is a synchronous exception made from a lower exception level (the operating system kernel running in EL1 to the hypervisor in EL2). If the operating system kernel is running in 32-bit mode the CPU will therefore jump to VBAR_EL2+0x600, or to VBAR_EL2+0x400 in 64-bit mode. After using the boot remapper to write custom code to this address and making a Hypervisor Call the CPU would start executing the shell code.
Unfortunately VBAR_EL2
is not readable by the operating system kernel running in EL1. It is only readable from the hypervisor (EL2) itself or higher. Still, this knowledge makes it much easier to guess the entry address using brute force: The base address of the vector table must be aligned to (a multiple of) its size (0x800 = 2 KiB). This means there are only 64 possible locations inside a region of 128 KiB, or 512 inside a region of 1 MiB:
The red boxes show all the possible locations where the CPU might jump to during the hypervisor call. Writing the shell code to all of these is sufficient to keep the approach independent of one particular firmware version (which will actually have the vector table at one particular address).
This could be improved further: The boot remapper allows both read and write access so it would be possible to add some heuristics based on the existing code/data read from the memory locations. It should contain valid AArch64 (A64) instructions and likely some repeated fill bytes such as NOPs or branch instructions. (The space for the 32 instructions per exception vector is often only partially used because it is easier to branch to a proper function with more space.)
The proof of concept code included in this repository is a modification of Qualcomm's open-source Little Kernel (LK) bootloader for the Snapdragon 410 (MSM8916/APQ8016) platform, originally intended to be tested with the DragonBoard 410c development board. All this was chosen for simplicity, the issue can also be exploited from other operating systems (such as Linux), other affected platforms and even devices with secure boot - as long as there is a way to execute custom code within the operating system kernel.
The code implements the approach described above to fully disable the running hypervisor and then replaces it with a different version. The new "hypervisor" does not support any virtual machines but is capable of giving the Answer to the Ultimate Question of Life, The Universe, and Everything using a simple hypervisor call:
$ 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
The proof of concept code can be built and tested as follows:
$ 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
...
However, this configuration is only suitable for the DragonBoard 410c development board and other devices without secure boot where the primary bootloader can be easily (and safely) replaced. The code in this repository is primarily provided for reference and not for easy usage/testing.
In the future it is planned to integrate the code into a new version of lk2nd, which can be tested much more easily on various devices without replacing the primary bootloader.
According to Qualcomm, the issue was fixed by disallowing access to the boot remapper region using the stage 2 translation. The boot remapper configuration (APCS_BOOT_START_ADDR_NSEC
register) is still accessible, but now both memory regions are blocked:
Another possible fix would be to protect the APCS_BOOT_START_ADDR_NSEC
register in the hypervisor. The remapped region could remain accessible in this case, since the operating system would have no way to reconfigure the boot remapper to other memory regions.
- October 2021: Issue reported to [email protected]; initial reply from Qualcomm.
- November 2021: Asked Qualcomm for update; they need more time for investigation.
- December 2021: Qualcomm confirms issue, recent chipsets are not affected but some older ones are still supported. They request a ~6 months embargo to develop and propagate a fix to customers.
- May 2022: Asked Qualcomm for update; they are still working on a fix for some platforms.
- June 2022: Qualcomm assigns tentative CVE number and requests extension of embargo until November.
- November 2022:
- Nov 7th: Asked Qualcomm for update since the issue was not published in the November bulletin.
- Nov 10th: Qualcomm assigns new CVE number, they plan to add it to the existing bulletin.
- December 2022:
- Dec 5th: Qualcomm publishes issue in December 2022 security bulletin.
- Dec 28th: Report published on GitHub (msm8916-mainline/CVE-2022-22063).
According to Qualcomm, the issue was particularly difficult to handle because their usual automation tools could not be used. Most issues they receive are software issues, where affected devices can be identified by checking the source code. For this issue it was required to check both hardware and software manually (if the platform has the problematic register and if the hypervisor is vulnerable). Unfortunately, not using the automation also meant that the issue was not automatically scheduled in a security bulletin. The second extension of the embargo was requested to make customers aware of the security issue and give them time to patch their devices. They are working on improving the process to avoid such issues for future reports.
- Qualcomm's December 2022 Security Bulletin
- Arm Architecture Reference Manual for A-profile architecture
- Qualcomm Snapdragon 410E Technical Reference Manual rev. D
- Qualcomm Snapdragon 410E Hardware Register Description
- ARM: Learn the architecture
- Qualcomm whitepapers
CVE-2022-22063 Report and Diagrams © 2022 by Stephan Gerhold are licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).
The proof of concept code (CVE-2022-22063.c
) is provided under the MIT License.