CVE-2021-3491: Triggering a Linux Kernel io_uring Overflow

Introduction:

Linux kernel vulnerability research has been a hot topic lately. A lot of great research papers got published lately. One specific topic that was interesting is io_uring.

At Haboob, we decided to start a small research to investigate one of the published CVEs, specifically CVE-2021-3491. 

Throughout this blogpost, we will explain io_uring fundamentals, its use-case and the advantage it offers. We’ll also walk through CVE-2021-3491 from root-cause to PoC development.

Everyone loves kernel bugs it seems, buckle up for a quick fine ride!         

Why io_uring?

Io_uring is a new subsystem that is rapidly changing and improving. It’s ripe for research!

It’s very interesting to see how it internally works and how it interacts with the kernel.

- io_uring: what is it?

According to the manuals: io_uring is a Linux-specific API for asynchronous I/O. It allows the user to submit one or more I/O requests, which are processed asynchronously without blocking the calling process. io_uring gets its name from ring buffers which are shared between user space and kernel space. This arrangement allows for efficient I/O, while avoiding the overhead of copying buffers between them, where possible. This interface makes io_uring different from other UNIX I/O APIs, wherein, rather than just communicate between kernel and user space with system calls, ring buffers are used as the main mode of communication.


Root Cause:

After checking the io_uring source code commit changes in (fs/io_uring.c), we start tracing the differences between the patched version and the unpatched version, and try to realize the cause of the bug.

We first notice that in struct io_buffer the “len”  is defined as sign int32 that is being used as the length for buffer.



Then, we also notice that in io_add_buffers, when attemping to access the struct: buf->len was assigned without checking the data type and MAX_RW_COUNT.



We found that there is a multiplication (p->len * p->nbufs) in io_provide_buffers_prep which leads to integer overflow when (p->len > 0x7fffffff) executes. Then it will bypass the access check during the access_ok() function call.



When we perform te  IORING_OP_READV operation with the selected buffer, we can bypass the MAX_RW_COUNT:



Using “R/W” on “/proc/self/mem” will force the kernel to handle our request using mem_rw function. From the arguments the “count” is received as size_t then passed to min_t() as an integer which will return a negative number in “this_len”.

Access_remote_vm function will receive “this_len” as a negative number which will result in copying more PAGE_SIZE bytes to the page, which results to a heap overflow.



Triggering the Bug:

We will go through the details of how the bug is triggered to achieve a kernel panic that can lead to a heap overflow.

 

Step1:

The following code snippet will interact with “proc” to open a file descriptor for “/proc/self/mem” and extract an address from “/proc/self/maps” to attempt to read from it:

Step2:

We need to prepare the buffer using the function “io_uring_prep_provide_buffers()” with length 0x80000000 to trigger the integer overflow vulnerability:

Step3:

Using iovec struct with 2 dimensional buffer, we assign the “len” as 0x80000000 to bypass MAX_RW_COUNT:

Step4:

When we do IORING_OP_READV operation on “file_fd” using offset “start_address” we can read the content of “/proc/self/mem” with that offset using the selected buffer:


POC

We can trigger kernel panic with the following PoC:

Previous
Previous

Exploring Chrome’s CVE-2020-6418 – Part1

Next
Next

Advanced “USN Journal” Forensics