In November 2017 a use-after-free bug to linux kernel was detected by syzkaller system. In February 2018 this was patched in some linux kernels and android versions.
This fix was never included in Android monthly security bulletins, so it was not patched in many newly released devices such as Pixel and Pixel2.
In September 2019 android was informed of the security implications of this bug by Project Zero. Then android assigned CVE-2019-2215 to this vulnerability to make it more formal and known.
CVE-2019-2215 is a use-after-free in binder.c that allows evaluation of privilege (getting root access) from an android application. There is no need to user’s interaction, to exploit this vulnerability. It only requires the installation of a malicious local application.
Here we are going to introduce this android kernel vulnerability in more details and use this vulnerability to get root access (privilege escalation) of the whole android device.
We will use this Proof of Concept (PoC):
https://github.com/cloudfuzz/android-kernel-exploitation
We will first show you a way to trigger this vulnerability on an android emulator and make a kernel crash. Then to see how it would be dangerous, we continue using PoC to get root access in the simulated android device. Then we will analyze the kernel code to see what is the reason (static and dynamic analysis).
After analysis, we will see how we got the root access. At last we see how this vulnerability is mitigated using patches.
To make a kernel crash by triggering this vulnerability we use this steps:
- First of all, you need a linux OS with 'gdb' and 'python' installed.
- Clone the PoC repository. (https://github.com/cloudfuzz/android-kernel-exploitation)
- Install android emulator and android NDK (By Installing Android Studio)
- Clone the android Kernel source code. (The 'q-goldfish-android-goldfish-4.14-dev' branch will be used)
- This kernel is already patched, we change it to reintroduce the vulnerability in this kernel code.
- Now We should build the kernel from source code. We build our kernel with KASan.
- We boot the built kernel and run our emulator with it.
- Then by using 'trigger.cpp' in the PoC repository, we trigger a crash (using 'adb' cmd).
- We will use 'root-me.py' in PoC and 'gdb' cmd to get root access in the emulated device.
Watch the following video:
[video]
In This (and next) section we are going to understand why the crash happens by using static and dynamic analysis.
Here we will analyze kernel code (static analysis) to understand the problem. In crash_report.txt there is the report from KASan which says this is a use-after-free bug. It means an object is allocated in heap (and we have a reference to it), then we freed the object from the heap and then we erroneously called it by a reference. In this report the stack trace of these three stages is printed.
If you remember from above, we used 'trigger.cpp' in the PoC.
Here is main code of trigger.cpp:
int main() {
//1
int fd, epfd;
//2
struct epoll_event event = {.events = EPOLLIN};
//3
fd = open("/dev/binder", O_RDONLY);
//4
epfd = epoll_create(1000);
//5
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
//6
ioctl(fd, BINDER_THREAD_EXIT, NULL);
}
Let's see what 'trigger.cpp' is doing.
In Android (like other Unix-based operating systems) we have some processes. Every program that we run creates one (or more) processes and these processes are handled by OS. OS may switch between them (multi-tasking) or terminate a process, etc. For security reasons, processes are isolated from each other by default.
In some cases, one process may need to exchange data with another process. It is called Interprocess Communication (IPC). There are several ways for processes to communicate in linux. Android introduced a specific IPC mechanism called 'Binder'. Binder is a kernel driver to facilitate inter-process communication.
In android, IPC can be done by directly calling some kernel methods (most of them are in drivers/binder.c) or using high-level implementations (for example in java).
For using binder, we should open the kernel binder module. It is done using line 3 of trigger.cpp. Then we have a file descriptor pointer, By using this fd kernel initiator and recipients of the IPC can be identified.
All interactions with the driver will happen through a small set of 'ioctl' commands (BINDER_THREAD_EXIT, BINDER_WRITE_READ, ...).
More about binder: link1, link2
In linux, we have a concept called 'event polling'. 'epoll' API is used when we want to monitor multiple file descriptors (file descriptors are what we have when opening a driver or working with IO, etc).
epoll is a kernel struct which has two important field.
- interest list = list of file descriptors we want to monitor.
- ready list = list of file descriptors that are ready for I/O.
Inorder to use event polling, we first create an epoll (line 4), then add or delete (EPOLL_CTL_ADD) an event (&event) associated with a file descriptor (fd) to our created epoll (epfd) by calling epoll_ctl method of kernel.
=> epoll_event event
is an event, triggered when the associated file (fd) is available for read operation.
Now we understand what trigger.cpp is doing (no need to go deep!). It opens the binder module, creates an epoll to listen to it when it is ready. Then at line 6, we exit from the binder that we started in line 3.
By calling open(), we actually call open_binder() ( open() implementation in binder.c ), in open_binder(), a new 'binder_proc' struct will be created and:
fd->pricate_data = binder_proc
By calling epoll_create(), a new epoll struct will be created and added to a queue structure.
By calling epoll_ctrl(epdf, ADD, fd, event), a new ep_item is created, associate fd (file descriptor to listen) to this ep_item, and it is inserted to event_poll's red black tree ( a data structure in ep to save ep_items ). It also calls ep_item_poll(), this method handles the call back function association to ep_item.
It creates new binder_thread struct (allocation happens here), links it to the binder_proc (created above), then an epoll_entry struct is created, it has two lists, epoll_entry->wait and epoll_entry->whead, both of these lists have a pointer to binder_thread created before.
Then epoll_entry is linked to ep_item (ep_item->pwqlist is a list that contains this epoll_entry).
By calling ioctl(fd, ...), binder_proc is accessed through fd->private_data, then the binder_thread struct will be freed from the memory.
When our current process exits, epoll_ctl(epfd, DEL, fd, event) will be called.
It calls ep_remove(event_poll, ep_item). This method gets the epoll_entry from ep_item->pwqlist, then gets the wait list of ep_item (ep_tem->wait), it is a linked list, it wants to remove one of this wait list's items.
It uses following code (pseudo code is used):
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
Here wait->entry is a pointer to binder_thread which was removed from memory! So it is a use after free and causes a bug!!
- In above, we used codes similar to actual kernel codes, they may be different in some details. (some cases binder_thread is used instead of binder_thread->wait)
We created an event_poll that has a red_black_tree, each node is an ep_item that has a field which is a list of epoll_entry, each epoll_entry has two pointers to binder_thread struct (wait, whead).
By calling ioctl() we freed the binder_thread from memory, Then while exiting this struct is accessed through a pointer which was still available!
Dynamic analysis is the testing and evaluation of a program by executing data in real-time ; to find errors in a program while it is running.
steps:
-
Build Android Kernel without KASan
We build it without KASAN to monitor write and unlink operations and what is really happening after unlink operation.
-
Boot emulator with the newly built kernel
-
Launch emulator
-
Build the vulnerability trigger and push it to the virtual device
-
Break in GDB
Load the custom python script(dynamic-analysis.py in repo) : To trace function calls and dump the binder_thread structure chunk before and after it's freed. Also dump the same binder_thread structure before and after the unlink operation has been done.
In this file we first delete all breakpoints and the put 2 breakpoints(BP); The first symbol is “binder_free_thread” (will trace binder_free_thread function)before binder_thread is freed, stop function will be called; so parameters and symbol will be shown with (gb.write(....) ) and then the callback method(we set it set_dump_binder_thread ) will be called; In this function binder_thread_address will be set in our global variable and gdb.execute send any output produced by command to GDB’s standard output .
The second symbol is “remove_wait_queue”(will trace remove_wait_queue function) the parameters which we want to observe are "wq_head", "wq_entry" and for exit wait.c:52 breakpoint will be set. Their callback function is dump_binder_thread .These breakpoints will show what happens before and after the unlink operation.
-
launch adb shell and run the trigger PoC
result:
- first part of result:
binder_free_thread(thread=0xffff88800c18f200)(enter) 0xffff88800c18f200: 0xffff88806793c000 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
in our python code we had below codes:
gdb.write (
"{function}({param})(enter)\n".format(
function=self.function_name, param=params
)
)
and it is binder_free_thread function so the parameter for this function is a pointer to binder_thread:
static void binder_free_thread(struct binder_thread *thread)
{
[...]
kfree(thread);
}
in the result we had(binder_free_thread(thread=0xffff88800c18f200)(enter)
).
lines after shows the result of execute , with below command we get offset of binder_thread.wait:
p offsetof(struct binder_thread, wait)
the result is 0xa0 and if we put wait.head instead of wait in command , the result will be 0xa8 and it contains 0xffff88805c05cae0
0xffff88805c05cae0 is pointer to eppoll_entry->wait.entry which is of type struct list_head
- second part of result
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
In remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter)
, wq_head is binder_thread.wait address and wq_entry is data of wait.head .
after this unlink operation will occur.
- Third part of result:
Breakpoint 3 at 0xffffffff802aa5be: file /home/ashfaq/workshop/android-4.14-dev/goldfish/kernel/sched/wait.c, line 53. remove_wait_queue_wait.c:52(exit) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88800c18f2a8 0xffff88800c18f2b0: 0xffff88800c18f2a8 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
The highlighted part is because of this .The result is after unlink operation and we can see that for unlinking write the address (0xffff88800c18f2a0 + 0x8
) to next and previous.
it means:
(pointer to binder_thread->wait.head)
= binder_thread->wait.head.next
= `binder_thread->wait.head.prev
In this part we will show how we can use this bug to get root access.
Remember from above, we had a binder_thread struct, it was freed and used again by using a pointer. This is the code of binder_thread struct:
struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
struct list_head waiting_thread_node;
int pid;
int looper; /* only modified by this thread*/
bool looper_need_return; /* can be written by other thread*/
struct binder_transaction *transaction_stack;
struct list_head todo;
bool process_todo;
struct binder_error return_error;
struct binder_error reply_error;
wait_queue_head_t wait;
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
struct task_struct *task;
};
One of the fields is pointer task_struct, this struct has a field called addr_limit.
When we want to access an address in a process, it will be checked that the address is in user-space or not, if it was in kernel-space this access should be blocked. This checking is performed by comparing our address with addr_limit, if our address is less than addr_limit we have valid access.
addr_limit is actually separating user-space from kernel-space, so by changing this field in task_struct we have full access to kernel-space and we can do anything!
Here we have two steps to exploit:
- finding the address of task_struct in kernel-space
- changing the addr_limit in task_struct
In kernel, we can perform vectored I/O, it means that we can write or read more than one chunk of data to or from a file descriptor (file, socket, etc).
Vectored I/O is done by using writev, readv, recvmsg methods and iovec struct.
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ \
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */ \
};
By using vectored I/O, we are doing I/O operations on an array of buffers (iovec). Each iovec has a pointer to a buffer (iov_base) and the size of buffer (iov_len).
For example, if we want to write an array of buffers into a file (fd), we call
writev(fd, iovecStack, count)
This method (like readv and recvmsg) first of all copies the iovec array (iovecStack) into kernel-space, then reads from these buffers and writes to fd.
- first part (copying iovec array into kernel-space) is similar in all three methods.
We can write and read buffers by using pipe , pipe is a struct that gives us two file descriptors, one for reading and one for writing. Pipe has a length in bytes, when one process writes to a pipe more than its length, pipe blocks in that process and waits for another process to read from that pipe (using the read file descriptor of that pipe).
Kernel tries to allocate memory (for a struct) according to its size. For example when we freed the binder_thread struct from memory (see static analysis), and after that if we have a struct with a size similar to binder_thread, it has a good chance to be allocated at the same place as the freed binder_thread.
First we create an iovecStack (array of iovec structs) with a size similar to binder_thread struct.
Then we free binder_thread from memory (see static analysis ‘free’ part)
Then we call writev() on this iovecStack. First part of this method, copies iovecStack in kernel space,
It is more likely that our iovecStack is allocated in the same place as freed binder_thread.
If we have enough iovec in iovecStack, memory at binder_thread’s location looks like this:
You see that iovecStack[10].iov_base and iovecStack[10].iov_len and iovecStack[11].iov_base will be at the same place as binder_thread’s wait.lock, wait.head.next and wait.head.prev fields.
In the ‘use’ part of the static analysis we saw that crash happened because of an access to wait part of binder_thread during the unlinking process (removing one item from a linked list).
During the unlinking process,wait.head is unlinked and next and prev fields of it would point to the wait field of binder_thread (unlinking).
Before the second part of writev happens (writing from iovecs to file), if we run the unlink process, iovecStack[10].iov_len and iovecStack[11].iov_base will be overwritten by a kernel address. Then after running rest of writev, when it wants to process iovecStack[11], it reads from iovecStack[11].iov_base (=address of wait in binder_thread) length of iovecStack[11].iov_len of data.
if iovecStack[11].iov_len is enough, we read from the wait field to the task_struct field of the binder_thread so we would have the task_struct pointer.
- during the free part in static analysis, all of binder_thread wouldn’t free and parts like task_struct remain (we didn’t mention this for simplicity).
So to get the task_struct pointer we do the following:
- we create a pipe and a stack of iovecs
- create binder_thread and event_poll and link them (like what we did for static analysis)
- fork a child process (now we have parent and child process)
- In parent process call writev, it will import iovecStack in kernel-space (before continuing, block writev by writing junk data in pipe for example.)
- in the child process, perform unlink. then read junk data so that the parent process is notified.
- in the parent process, continue the rest of writev() and read from iovecs in kernel-space and write to file.
- Now the file contains reading from wait to below, of the binder_thread in kernel-space (task_struct is 0xe8 bytes after the wait field).
see exploit.cpp in repo.
Now we have the address of task_struct in kernel-space (task_ptr).
Here we use socket_pair instead of pipe. And we use recvmsg() for reading from socket and writing to iovecs.
Steps:
- first we do initializations like above
- fork a child process
- write some junks in socket_pair
- in parent process call recvmsg():
- it imports iovecs in kernel-space
- it then reads the junks and waits (blocks) for receiving other data from the socket.
- in child process perform unlink operation
- in child process write this data in the socket:
static uint64_t finalSocketData[] = {
0x1, // iovecStack[10].iov_len
0x41414141, // iovecStack[11].iov_base
0x8 + 0x8 + 0x8 + 0x8, // iovecStack[11].iov_len
(uint64_t) ((uint8_t *) task_ptr + OFFSET_OF_ADDR_LIMIT_IN_TASK_STRUCT), // iovecStack[12].iov_base
0xFFFFFFFFFFFFFFFE // addr_limit value
};
After writing, recvmsg() starts reading from socket and writing into iovecStack. Because of junk, it has written up to iovecStack[10].
So it start writing finalSocketData in iovecStack[12], gets the address from iovecStack[12].iov_base, which is address of** wait in binder_thread **because of unlink operation, iovecStack[12].iov_len is set 4 bytes, so writes:
- 0x1 into iovecStack[10].iov_len
- 0x41414141 into iovecStack[11].iov_base
- 0x8 + 0x8 + 0x8 + 0x8 into iovecStack[11].iov_len
- pointer_to_addr_limit into iovecStack[12].iov_base
now 4 bytes have been written in iovecStack[11] so recvmsg() goes to iovecStack[12] to write rest of finalSocketData:
writes 0xFFFFFFFFFFFFFFFE into the address in iovecStack[12].iov_base which is set to pointer_to_addr_limit, it means that recvmsg() changes addr_limit to 0xFFFFFFFFFFFFFFFE (not 0xFFFFFFFFFFFFFFFF because of some problems with arm64).
Now the user-space expands to approximately all kernel-space! (and can do anything!!)
binder_poll() passes the thread->wait waitqueue that can be slept on for work. When a thread that uses epoll explicitly exits using BINDER_THREAD_EXIT, the waitqueue is freed, but it is never removed from the corresponding epoll data structure. When the process subsequently exits, the epoll cleanup code tries to access the waitlist, which results in a use-after-free.
Prevent this by using POLLFREE when the thread exits.
we had this code:
static int binder_thread_release(struct binder_proc *proc, struct binder_thread *thread)
{
.
.
.
int active_transactions = 0;
.
.
.
binder_thread_dec_tmpref(thread);
return active_transactions;
}
These lines have been added to source code:
static int binder_thread_release(struct binder_proc *proc, binder_thread *thread) { . . . /* * If this thread used poll, make sure we remove the waitqueue * from any epoll data structures holding it with POLLFREE. * waitqueue_active() is safe to use here because we're holding * the inner lock. */ if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) { wake_up_poll(&thread->wait, EPOLLHUP | POLLFREE); } binder_inner_proc_unlock(thread->proc); /* * This is needed to avoid races between wake_up_poll() above and * and ep_remove_waitqueue() called for other reasons (eg the epoll file * descriptor being closed); ep_remove_waitqueue() holds an RCU read * lock, so we can be sure it's done after calling synchronize_rcu(). */ if (thread->looper & BINDER_LOOPER_STATE_POLL) synchronize_rcu(); . . . }
see full code here
operating system is a layer of software which is responsible for making all of the hardware work more efficiently and building an infrastructure on top of which application you use can work, its core is kernel.
The idea behind exploitation is simple: software has bugs, and bugs make software misbehave or incorrectly perform a task it was designed to perform properly ; exploiting a bug means turning this misbehavior into an advantage for attackers.
The bugs which are exploitable are referred to as vulnerabilities.
A privileged user or process is one who has full access of the device.
Most instruction set architectures provide at least two modes to execution:
privileged : All of the machine level instructions are accessible.
unprivileged : only a subset of the instruction are accessible.
Use-After-Free vulnerabilities are a type of memory corruption flaw that can be leveraged by hackers to execute arbitrary code.
Use-After-Free specifically refers to the attempt to access memory after it has been freed, which can cause a program to crash or, in the case of a Use-After-Free flaw, can potentially result in the execution of arbitrary code or even enable full remote code execution capabilities.
Is a use-after-free in Binder in the Android kernel.The bug is a local privilege escalation vulnerability that allows for a full compromise of a vulnerable device. If chained with a browser renderer exploit, this bug could fully compromise a device through a malicious website.It is reachable from inside the Chrome sandbox.
Note : It works on Pixel 1 and 2, but not Pixel 3 and 3a.see this attack.
Android security bulletins is a list published by google (monthly). This list contains fixed security vulnerabilities which affect android framework, linux kernel, etc.
Syzkaller is a kernel fuzzer. Fuzzing is a testing technique in which an automated program produces semi-random inputs for a target program to check if any bug is triggered. Fuzzing is especially useful in finding memory corruption bugs in C or C++ programs.
Project Zero is a team of security analysts employed by Google tasked with finding zero-day vulnerabilities. A zero-day vulnerability is a vulnerability that is unknown by those who should fix it.
GDB stands for GNU Project Debugger which is the most popular debugger for UNIX systems to debug C and C++ programs.GDB allows you to run the program up to a certain point, then stop and print out the values of certain variables at that point, or step through the program one line at a time and print out the values of each variable after executing each line.
You can connect your android emulator with a gdbserver for debugging.
Kernel Address SANitizer (KASAN) is a dynamic memory error detector designed to find out-of-bound and use-after-free bugs. KASAN uses compile-time instrumentation to insert validity checks before every memory access, and therefore requires a compiler version that supports that. Kernel memory accesses can be checked against the shadow map to see if they are valid.
QEMU (Quick Emulator) is a free and open-source emulator that performs hardware virtualization.The Android Emulator is downstream from the QEMU emulator ; It adds support for booting Android devices, emulates typical Android hardware (OpenGL, GPS, GSM, Sensors) and a GUI interface. The android emulator extends qemu in various ways.
In static analysis, We use the source code of a program to find a bug or any problem. We don’t run the program.
In Dynamic analysis, We analyze a program's behaviour while it is running. For example by providing special inputs.
Android NDK is a tool-set that lets you run native code like C and C++ in an android device.
Android goldfish kernel is used to run kernel code in an android emulator. It can be cloned and changed and then built to use in an emulator.
ADB is a command-line tool. It helps to communicate with a running android device and get a shell from it. It can be used for debugging purposes.
https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html
https://nvd.nist.gov/vuln/detail/CVE-2019-2215
https://www.tutorialspoint.com/gnu_debugger
https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
https://android.googlesource.com/kernel/goldfish/
https://man7.org/linux/man-pages/man7/epoll.7.html
https://www.scaler.com/topics/c/debugging-c-program/
...