Huawei's security hypervisor is leveraging the
virtualization extensions of the ARMv8-A architecture. Additionally, it makes use of
ARM TrustZone - a hardware enforced separation from called Normal World an Secure World. When communicating between the worlds, a
Secure Monitor is used. The Secure Monitor runs at EL3, the highest privileged level with TrustZone. The implementation is based around the
AFT project provided by ARM.
The Secure Element (SE - aka HISEE) is a peripheral on Hisilicon-equipped devices. for monitoring only accessible in the Secure World. Of course, the Normal World needs a way to access this; this is done via the Secure Monitor with SMC commands and the hisee driver.
In the Linux kernel, it is important that a user making a syscall doesn't provide addresses that exist in the kernel; hence, there is a wrapper function that does all of the sanity checks for us. A similar function exists within the Secure Monitor code. However, there are locations where the addresses are not validated! As a result, an attacker can pass in a addr and size outside of the shared CMA region. We can either 0xAABBCC55 to our address + 0x4 or a value between 0x0 and 0xC to the address X + 0xC.
The vulnerable command is CMD_HISEE_FACTORY_CHECK used in the logging component shown above. They chance the address of g_cma_addr to be 0xC and the size to 0xAABBCC55. This changes the range of allowed addresses from the CMA region to being practically infinite. With the secure monitor address space verification defeated, we can use other functions to perform even worse operations.
Using the function CMD_HISEE_FACTORY_CHECK a third time will from the SE to a destination address of our choice. This primitive can be used to hijack many of the functions pointers in the data section; I am assuming there are not write protections in the Secure World. They overwrote one of the SMC handlers to be 0x14230238 since there the gadget BLR X2, which can be used to jump to an arbitrary location with the controlled X2 register. They use this to obtain a temporary arbitrary write primitive with ROP.
They create an even better read/write primitive by using the primitive above to overwrite even more SMC handler pointers. The next step was getting full code execution, even though the code sections as labeled as read only. This was done by remapping the the same physical memory to a different mapping as RWX instead of just read only. With the double mapped address, code execution is gained by writing shellcode.
There is an issue with shared memory checks as well. There is functionality that simply copies logs. When performing this operation, the input from the user is an
address and
size. The code is shown below for sanity checks:
buf_addr >= 0x3c000000 && buf_addr + buf_size - 1 < 0x40000000
Do you see the mistake? The second check can UNDERFLOW if the value is too large. Hence, the check would pass. This oversight allows for us to write addresses relative to our chosen buffer address with some limitations on the locations of memory we can overwrite. The shared control structure for logging (hlog_header) is used to keep track of the current position in the shared memory buffer. The structure is in a modifiable location from our attack, allowing us to modify its fields, including a size and address value.
Using a memset gadget that sets bytes to zero, the author of the post overwrites a function pointer close to the gadget BLR X2 with 00 in the first byte (which is just where it points!). From there, they use the same exploitation strategy as before.
Overall, an amazing article! Great diagrams, code snippets, explanations and background on strange functionality. I wish more articles were like this!