176 - JS Type Confusions and Bringing Back Stack Attacks
A JIT optimization based type confusion in jscript9. The root cause of this bug is the fact that the OptArraySrc
optimization would call ShouldExpectConventionalArrayIndexValue()
to decide if it should keep a type check in place, but that function could sometimes return false and cause the optimization to remove a type check when it shouldn’t. The POC takes advantage of this by creating an Int32Array object (q
) that uses a backing ArrayBuffer object (g
), and a raw object (e
) with some properties. It also contains a boom(m)
function which would behave differently depending on m
. If m
was not set, it would read the first Int32Array value from q
then essentially do nothing. If m
was set, it would switch q
to e
and try to write to q[0]
.
They then call boom
with an m=false
100k times to get it JIT’d, and train the JIT to believe it doesn’t need the type check anymore. It then calls boom
with m=true
, which switches the type and ends up writing using e
instead of the Int32Array, giving arbitrary write via e.e
’s pointer which jscript9 thinks is the pointer to the int32array’s backing ArrayBuffer. Ultimately, this achieves a relative read/write primitive which can be taken to arbitrary read/write and subsequently code execution fairly easily via causing more type confusions in the browser.
A post on exploiting a bug that Jann Horn discovered in the linux kernel’s memory management (MM) subsystem. The bug isn’t detailed in this post and is fairly complex (there is a project zero bug report but it’s difficult to understand without deep knowledge of MM internals), though they state it will be written up in a future blogpost. What’s important to takeaway from the bug is that it essentially gets two VMAs merged with different secondary anon_vma
, and subsequently have some mapped pages with a dangling page->mapping
pointer (or folio->mapping
on later kernel versions). This UAF can be triggered by calling madvise()
on the affected page, which eventually calls folio_lock_anon_vma_read()
.
Restricted write
Looking at folio_lock_anon_vma_read()
, they found there was a down_read_trylock()
call that would take a pointer to a reader writer semaphore that came from the UAF’d object. This gave them a restricted write primitive, which would increment a uint64 by 0x100 assuming the most significant bit and the 3 least significant bits weren’t set. This effectively prevents this write from being able to corrupt kernel pointers or anything that isn’t aligned to an 8-byte boundary. KASLR is also a factor, and so this seems like a tricky exploit scenario. Fortunately, it’s possible to take advantage of the fact that there’s non-randomized CPU entry area stacks to store register context (Interrupt Stack Tables / ISTs). By triggering an exception (such as #DB
) in a copy_to/from_user()
, the register context could get dumped to this stack on interrupt and be restored when the thread resumes.
Getting stack OOB read/write + code exec
By intentionally triggering an exception on a copy_to_user()
from a stack address, they could then use the restricted write primitive to increment the register containing the length of the copy to get an out-of-bounds read and leak stack data (including the stack cookie and pointers to defeat kASLR). Conversely, by doing the same on a copy_from_user()
call to a stack address, they could smash the return pointer to get code execution, while maintaining the cookie value they leaked earlier.
Excellent post covering three vulnerabilities in Huawei’s Secure Monitor used to proxy/transition requests from the “normal world” usually from the hypervisor or kernel into the secure world.
SMC SE Factory Check OOB Access - CVE-2021-39994
For communication between the normal and secure world a shared memory buffer is used and should exist within a specific contiguous region (0x40000000-0x50000000). An incoming command will include an address to the shared buffer that the output should be written to. There is a function se_smc_addr_check
which is used to ensure that the address and the data size is completely contained within the expected region.
This function is used inconsistently and there are some commands that do not actually check the bounds of the provided address, allowing an attacker to have output written into the secure world memory. se_factory_check
is one of these functions. It will write a code
value out to the provided address. Allowing them to clobber other sensitive values.
SMC MNTN - OOB Access
The mntn system supports some of the logging functionality, and provides a way to get logs from the secure system. The first command to the system should be the init command which sets up the physical address and size of the shared buffer to use, it then allocates the shared buffer. It does attempt to ensure the buffer will be in the appropriate range:
// Check (improperly) that the buffer is in the address range 0x3c000000-0x40000000.
if (buf_addr >= 0x3c000000 && buf_addr + buf_size - 1 < 0x40000000) {
The problem is that buf_addr + buf_size
can overflow, resulting in a value smaller than the limit so the checks pass.
SMC MNTN - Shared Control Structure
There is a second issue in the mntn code. The system will write a control structure to the head of the buffer. This structure (hlog_header
) contains the relevant meta-data for writing to the log. It tracks the address, along size the current position and maximum size. The problem here is that the shared buffer, is, well, shared. As teh control structure is within the buffer, the kernel (normal world) can tamper with any of these values and control where logs will be written.
The post also goes into exploitation of the above issues to gain secure world code execution.