Skip to content

Latest commit

 

History

History
129 lines (129 loc) · 7.25 KB

File metadata and controls

129 lines (129 loc) · 7.25 KB
  1. Jump out of the slab – Page Spray Most kernel exploitation strategies leverage spray objects to overwrite kernel memory. For example, for use-after-free memory corruption, spraying data objects whose content is fully controlled by attackers could overwrite the data in the UAF object. This technique is known as heap spray. Some elastic objects 4 Figure 2: The memory layout before and after triggering the free of io_uring_task are used very often due to their "clean and stable" memory allocation capability. However, object spray is not always available, it requires the kernel to enable some specific subsystem. When we first tried to find a spray object to manipulate the memory layout for the bug in Android, we failed because we did not find a suitable object. Either due to restricted access, or the functionalities is just not available. In the end, we tackle this challenge from a different perspective – instead of spraying objects, we overwrite the target memory by spraying the memory pages. The page is the basic unit of memory management in the Linux kernel. The slab, which is known as the allocator for kmalloc, is built on top of it. By overwriting the memory page of the object in the slab, we could also overwrite the memory of the UAF object. 4.1. The Page Spray Technique As is discussed in AUTOSLAB [ 3 ], the memory page of a slab will be recycled to the buddy allocator when all the objects in the slab are freed. Therefore, by reclaiming the freed page, we could overwrite the data to craft a fake object to the dangling pointer. Figure 2 demonstrates this idea. Initially, in (a), the UAF object contains a function f ptr, a PC control could be obtained by overwriting it. To do so, we could trigger the vulnerability, free the UAF object, and then free all the other objects on the same slab. As is shown in (b), the page for the slab eventually gets freed, the dangling pointer refers to the freed page. Next, we reclaim the freed page by spraying kernel pages, which contain crafted data and tamper the function pointer. 5 As we will describe below, there is no limitation for spraying pages in the Linux kernel. Therefore, the technique gives us a reliable way to craft any data for the io_identity object, and eventually contributes to the success of the exploitation. 4.2. Page Candidates The page spray technique requires allocating pages that are shared with the slab allocator. Through manual analysis, we found that there are some APIs accessible in the Android kernel without the limitation of allocation, as such, we could implement the page spray. static void *io_mem_alloc(size_t size) { gfp_t gfp_flags = GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN | __GFP_COMP | __GFP_NORETRY; return (void *) __get_free_pages(gfp_flags, get_order(size)); } One API function we manually found is in the io_uring subsystem. The io_uring has a ring buffer shared between userspace and kernel space. The ring buffer is allocated through the buddy allocator to obtain kernel pages (function io_mem_alloc above). As a shared memory, userspace could directly modify the page content, this means of the page is overlapped with the kernel object, we could tamper the kernel object directly from the userspace. In addition, users can control the size of the page to reclaim the freed slab page as long as the size does not exceed the system memory limit. static ssize_t pipe_write(struct kiocb *iocb, struct iov_iter *from) { ... if (!page) { page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); if (unlikely(!page)) { ret = ret ? : -ENOMEM; break; } pipe->tmp_page = page; } ... } Another API function we found is in the pipe subsystem. The page allocated is to hold to buffer written to the pipe. By writing crafted data to the pipe, we could control the content stored on the memory page. Besides, the page could 6 Figure 3: The high-level exploitation flow be freed by draining the buffer in the pipe. Compared to the page allocation in io_uring, the allocation here is more restricted – we cannot control the size of the page. In addition, we cannot modify the data on the page from userspace directly. Modifying data can only be done through reading and writing to the pipe.
  2. Achieving Arbitrary Read/Write With the page spray technique, we could tamper the UAF object with anything we want. However, the UAF object itself contains nothing that can be sent to userspace. Additionally, interacting with it will dereference a series of pointers. Without an information leak, we could not leverage any memory corruption to achieve the goal. In this section, we detail how to address the challenge to achieve the arbitrary read and write capability in Android. To address the challenge, one thing we could do is to pivot the vulnerability capability. Originally, the UAF object is the ioidentity which is hard to leverage due to its nature. By pivoting the vulnerability capability, we could make another object in the same slab as the vulnerable object. Step (a) - (c) from Figure 3 depict the process of such a capability pivot. In step (a), we allocate an object called pipe_buf f er in the same slab of the UAF object, the figure shows the memory layout of the slab. Next, in step (b), we trigger the vulnerability, which will free the io_uring_task and the UAF object identity. This triggering will make the allocator think two objects on 7 the slab are freed. Later on, we free the other objects on the slab and keep the pipe_buf f er intact (step c). Since the whole slab is freed, the memory page for the page will be marked as freed as well. As such, the pointer to the pipe_buf f er becomes a dangling pointer to a freed memory, as is shown is step (d). With the help of the page spray technique, now we could reclaim the freed slab page and overwrite the pipe_buf f er object in step (e). struct pipe_buffer { struct page *page; unsigned int offset, len; const struct pipe_buf_operations *ops; unsigned int flags; unsigned long private; }; 5.1. Why pipe_buffer The code above is the definition of the pipe_buf f er object, which contains a page field that indicates the page where the buffer for the pipe is stored, as well as a function table pointer ops, which depicts the API functions the pipe is used. Those two fields can be leveraged to leak kernel information and achieve arbitrary read and write. When the users write data to the pipe, the pipe subsystem will check if the pipe_buf f er is initialized or not. If not, the pipe_buf f er will be filled with a pipe_ops, which is a global function table pointer indicating the operators of the pipe. Since we have reclaimed the page through the page spray technique in the userspace, we now have read and write capability to the page. By reading the content from the page, we could leak the pointer, thus bypassing KASLR (step (e)). Additionally, the page pointer is the destination that the pipe will read and write, by tampering the page pointer to anywhere, we could achieve arbitrary read and write by simply reading and writing to the pipe, as is shown in step (f). With the function pointer leaked and arbitrary read and write capability on hand, there is not more mitigation we need to bypass in AOSP. Therefore, to exploit Google’s Pixel, we traverse the kernel task finding our current process and cred, and then change the uid to escalate our privilege. We could also disable SELinux by simply overwriting the SELinux state.