Kernel developers, if not aware of kernel vulnerabilities, are prone to introduce vulnerabilities during development, or at least cause kernel crashes, affecting all applications above; On the other hand, it leads to kernel empowerment, which means that you can break out of the application layer sandbox, enter the kernel, and do whatever you want inside the kernel.

The purpose of this article is to explain the common types of kernel vulnerabilities, so that kernel developers have a preliminary understanding of the development of the subconscious, not to create more obvious vulnerabilities. (this article is not necessary for those who specialize in kernel vulnerabilities :).

Common kernel vulnerabilities are as follows: Stack overflow, heap overflow, Integer overflow, use-after-free, double free, thread race Condtion).

The following is a brief introduction through examples:

Stack overflow.

Example:

static size_t deviceA_write(struct file *filp, const char __user *buf, size_t len, loff_t *data) 
{
     int ret = 0;
     char tmp[100] = { 0 };

     /* write data to the buffer */
     if (copy_from_user(tmp, buf, len)) {
                return -EFAULT;
}
Copy the code

The above is a device driver code that provides an interface to write. That is, users can call the write function at the application layer to write data to the device. Kernel developers know that copy_from_user copies data from the application layer to the kernel layer. Its first parameter TMP is the destination address of the copy. The second parameter buf is the source address of the copy. The third parameter is the length of the copied data. In this case, the length of the target address is 100, but since there is no limit on the length of the copy (len), when the length passed by the user is greater than 100, the user’s data will overwrite the data beyond 100 bytes of TMP. Since the stack is stacked from high to low, the data overwritten will be at a higher address than TMP. The return address of the program is usually placed on the stack during function calls. If the return address is overwritten, the instruction counter IP can be controlled and the code can be executed at the kernel level. (Kernel mitigation measures are not considered here for the time being)

Heap overflow:

A heap overflow is similar to a stack, except that the object is a heap. For example, replace char TMP [100] with char * TMP = kmalloc(100); Then it becomes a heap overflow vulnerability.

Integer overflow:

Integer overflow is the operation of integers without considering the lower and upper limits of the integers themselves. For example, the lower limit of an unsigned int is 0 and the upper limit is 0xffffFFFF. When a, B, and C are all unsgined int types, a = B + C may overflow, resulting in a being smaller than B or C. For example, if b = 0xffFFFFFF, c = 2, then a = b + C = 0xFFFFFFFF + 2 = 1. The sum of the two numbers is actually getting smaller!

Example:

1  int driver_create_buffer(Command *userCommand)
2  {
3.4       uint32_t totalCount = 0;
5       uint32_t *regAddrBuf = NULL;
7       uint32_t kernelCount = 1000;
8       const uint32_t userCount = userCommand->count;
9.10      totalCount = kernelCount + userCount;
11      regAddrBuf = kzalloc(totalCount * sizeof(uint32_t), GFP_KERNEL);
12      copy_from_user(regAddrBuf, userCommand->buffer, userCount*sizeof(uint32_t));
13.14   }
Copy the code

In line 10 of the above code, add the positive integers kernelCount and userCount and store them in totalCount. UserCount can be controlled by the user, and overflows can occur when the value is set to large, such as when userCount= 0xFFFF-1000 + 1, then totalCount is 1. In line 11, the space is allocated according to totalCount, and in line 12, the length of the copy is userCount, which is significantly larger than totalCount. This can lead to overflow vulnerabilities.

Reuse after release:

As the name implies, a kernel object that is released and then used. This vulnerability of the use of technology has been very mature, here is not introduced to use 🙂

1.  static int dealloc_session(void *core_handle,
2.      struct session *session)
3. {
4..5.      deregister(session->handle);
6.      mutex_destroy(&session->lock);
7.      sessions[session->id] = NULL;
8.      kfree(session);
9.      session = NULL;
10..11. }
Copy the code

Once all session content members are freed, kfree the session itself and set it to NULL immediately. But there is no consideration of multithreading here. For example, after a thread releases the session kernel object on line 8, another thread is accessing the object on line 5. This creates a post-release reuse vulnerability.

Double release:

Double release, which means to release an object twice. So the developers said, release twice, release twice, can we use that? So I say: can use! Double-release vulnerabilities can be converted to post-release reuse vulnerabilities. And the technology is very mature. Here’s an example:

1. static long deviceB_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
2  {
3..4.     if (__copy_from_user(idc, (void __user *)arg, tmp)) {
5.         kfree(idc);
6.         err = -EFAULT;
7.     } else {
8.         err = spi_message(spi, idc, n_ioc);
9.     }
10.    kfree(ioc);
11.    return 0;
13. }
Copy the code

Ioctl is familiar to kernel developers and allows you to add new functionality to device drivers. The user layer can call iocTL directly to use the new functionality of the device. In the example above, line 5, the IDC kernel object is released, but not returned. It is released again in line 10, causing a double release of the kernel object IDC.

Thread contention:

Thread contention vulnerability is not an independent vulnerability. Generally, thread contention leads to post-release reuse vulnerability, double release vulnerability or heap overflow vulnerability. For example, the above post-release reuse vulnerability is caused by thread contention, which will not be further explained here.