Summary

In this advisory we are disclosing a vmap/vmalloc use-after-free vulnerability within the Android ION allocator, that impacts most Android devices that utilize ION. The exploitability of the vulnerability depends on how the various kernel drivers use the allocated ION buffers, the version of the kernel and the vendor specific modifications to the ION subsystem. In the most serious cases the vulnerability can be used to corrupt vmallocated kernel buffers, including kernel stacks, to achieve kernel code execution and compromise the integrity of the kernel.

The ION allocator went over multiple refactors throughout its evolution, yet the vulnerability persisted within the Android common and the Linux upstream kernel branches. Some vendor modifications, accidentally mitigate the vulnerability. We suspect that a very significant portion of the Android ecosystem is affected, however we don’t have the resources to provide a complete impact analysis.

Currently, as of September 2021, this vulnerability is still a zero-day on Pixel 3 and potentially many other vendor devices. The Android Security Team refused to communicate how or when they intend to release a patch for the bug. As the ninety days have passed since the original report hereby we are publicly disclosing this vulnerability in accordance with our disclosure policy. To make matters worse, a manifestation of this vulnerability has already been reported and publicly disclosed by Man Yue Mo in January 2021.

Vulnerability Details

The ION allocator is an extensible memory management framework that facilitates the allocations of so called dma-buffers. These buffers are represented by a file descriptor and can be shared between user-space applications, kernel drivers and devices, based on the given device drivers’ needs. When the kernel needs to access an ION allocated buffer it either calls ion_map_kernel(), dma_buf_vmap(), or dma_buf_begin_cpu_access() and dma_buf_kmap() (depending on the version of the implementation or the context of the call). All of these calls end up calling ion_buffer_kmap_get() (see the extract below), that increments the buffer’s reference counter and calls the heap specific memory map function if needed. For the most common ION heap implementations (system, carveout, system contiguous) the map_kernel function simply vmap()’s the buffer pages and returns the resulting kernel virtual address.

static void *ion_buffer_kmap_get(struct ion_buffer *buffer)
{
  void *vaddr;

  // [1]
  if (buffer->kmap_cnt) {
    buffer->kmap_cnt++;
    return buffer->vaddr;
  }
  // [2]
  vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer);
  if (WARN_ONCE(vaddr == NULL,
      "heap->ops->map_kernel should return ERR_PTR on error"))
    return ERR_PTR(-EINVAL);
  if (IS_ERR(vaddr))
    return vaddr;
  // [3]
  buffer->vaddr = vaddr;
  buffer->kmap_cnt++;
  return vaddr;
}

At [1] the function checks if the buffer has been mapped before, if it has, the saved virtual address is returned and the reference counter is increased. If the buffer has not been mapped, the heap implementation specific map_kernel callback is called at [2]. The returned kernel virtual address is saved for later and the reference counter is increased at [3].

The respective ion_unmap_kernel(), dma_buf_vunmap(), dma_buf_end_cpu_access() and dma_buf_kunmap() functions all call the ion_buffer_kmap_put() function. This function decrements the buffer reference counter and if the counter reaches zero, calls the heap specific unmap_kernel() function.

static void ion_buffer_kmap_put(struct ion_buffer *buffer)
{
  buffer->kmap_cnt--;
  if (!buffer->kmap_cnt) {
    buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
    buffer->vaddr = NULL;
  }
}

The unmap_kernel callback is set to ion_heap_unmap_kernel for the most common heap implementations (system, system contiguous, carvout and chunk heaps). It simply vunmap()-s the buffer.

void ion_heap_unmap_kernel(struct ion_heap *heap,
			   struct ion_buffer *buffer)
{
	vunmap(buffer->vaddr);
}

The vulnerability comes from the fact that the allocated ion buffer implements the dma-buffer API, which exposes a series of IOCTL functions through the buffer’s file descriptor. The dma_buf_ioctl() implements the DMA_BUF_IOCTL_SYNC operation which can be used to call the dma_buf_end_cpu_access() and dma_buf_begin_cpu_access() functions ([4]) arbitrary times.

static long dma_buf_ioctl(struct file *file,
        unsigned int cmd, unsigned long arg)
{
  struct dma_buf *dmabuf;
  struct dma_buf_sync sync;
  enum dma_data_direction direction;
  int ret;

  dmabuf = file->private_data;

  switch (cmd) {
  case DMA_BUF_IOCTL_SYNC:
    if (copy_from_user(&sync, (void __user *) arg, sizeof(sync)))
      return -EFAULT;

    [...]

    if (sync.flags & DMA_BUF_SYNC_END)
      // [4] 
      ret = dma_buf_end_cpu_access(dmabuf, direction);
    else
      ret = dma_buf_begin_cpu_access(dmabuf, direction);

    return ret;

    [...] 
  default:
    return -ENOTTY;
  }
}

The dma-buffer API can be used to decrement the reference count of an ION buffer to zero, thus force its unmapping from kernel memory. The kernel might still be accessing the same buffer through the same kernel virtual address for reading or writing depending on how a given platform uses ION buffers. A kernel device driver that imports a user allocated ION buffer, or alternatively allocates and exports one, and maps such buffers for kernel access is vulnerable to this type of use-after-free.

A malicious user-space process can allocate an ION buffer and pass it to a vulnerable kernel device. The device would map the buffer into the kernel virtual address space. Then the application can call the DMA_BUF_IOCTL_SYNC ioctl on the buffer fd to force the vunmap() call on the buffer. The application can spray critical kernel structures that are vamapped or vmallocated to overlap with the buffers previous kernel virtual address. This creates a vmalloc/vmap use-after-free situation, as both of these functions reserve virtual memory addresses from the same address space. Once the device driver proceeds to access the ION buffer through its kva, it will operate on the overlapped kernel data.

Brief Impact Analysis

The exploitability of this vulnerability is highly dependent on how the target platform uses the ION allocator. While the root cause of the vulnerability is present in the Android common kernel, the allocator itself is only used by platform specific drivers. To accurately asses the impact of the vulnerability each kernel device driver that maps and accesses ION buffers would have to be audited within the entire Android ecosystem. Such research is beyond our resources, however we did a cursory analysis for the largest vendor kernels (msm, samsung and huawei) to evaluate the impact.

We found that there are major discrepancies even within a single vendor, in terms of how the ION allocator is utilised. Some vendors heavily rely on the ION allocators, while others barely use it. Some platforms expose the vulnerability through device drivers that are available from untrusted application context, while on other platforms the vulnerability can only be exploited from privileged contexts. We have seen vendor specific modifications to the ION allocator, that by chance, completely mitigate the vulnerability. Overall we believe a significant portion of the Android ecosystem could be impacted.

According to our preliminary analysis, the following ION versions are used by major smart phone vendors. We do not have any information about how the ION allocator is modified on the billions of other embedded devices, smart phones and appliances that use Android.

  • Upstream Kernel (Linus’s branch)
    • 3.14 - 4.11: Ion Driver V1, Vulnerable
    • 4.12 - 5.9: Ion Driver V2, Vulnerable
    • 5.9+: Ion Driver is removed
  • Android Common
    • 4.9: Ion Driver V1, Vulnerable
    • 4.14/4.19: Ion Driver V2, Vulnerable
    • 5.4/5.10: Ion Driver V3, Most likely not vulnerable
  • Pixel:
    • 4.19.135 (msm-redbull) Pixel 5/4a(5g): Ion Driver MSM, Most likely not vulnerable
    • 4.14.199 (msm-coral/sunfish) Pixel 4/4a: Ion Driver MSM, Most likely not vulnerable
    • 4.9.237 (msm-bonito/crosshatch) Pixel 3/3a: Ion Driver V1-MSM, Vulnerable
  • Huawei:
    • 4.14.116 (Kirin990/Kirin980/Kirin970/Kirin810/Kirin710): Ion Driver HW, Most likely not vulnerable
  • Samsung:
    • 4.19.87 (S20 Android 11): Ion Driver V2-SS, Vulnerable

The 4.14 and 4.19 msm kernels use a heavily refactored version of the ION allocator where the dma_buf_begin/end_cpu_access() functions are not responsible for calling ion_buffer_kmap_get/put(). Instead they simply ensure cache coherency for the given buffer. As a result these implementations are not vulnerable.

The Huawei implementation is very similar to the 4.14 msm kernel’s, where the dma_buf_begin/end_cpu_access() solely responsible for cache maintenance and doesn’t invoke the ion_buffer_kmap_get/put().

The Samsung version is fundamentally very similar to the upstream V2 version it contained a few platform specific modifications. Besides the root cause being present on the S20 there were numerous device drivers that used the ion allocator in a vulnerable manner, including the Mali GPU driver and the Vertex NPU driver. The vulnerability was originally found on this device.

The Pixel 3 was also found vulnerable, with multiple drivers mapping ION buffers in an exploitable manner, including the QSEECOM device, camera device and DRM framework.

Mitigation

Google’s Android Security Team told us that the issue “remains in mediation” but refused to provide additional details about their remediation strategy, or when they would release a patch. We suggest consulting your device vendor if they are aware of this issue, whether it impacts them and when they plan to release mitigation.

While the duplicate bug report is restricted for us, we searched for its id (187527909) before the release of this advisory. We found the following change ID: I00dc8eefefb1f3aab99e770f90d624011f7740f0, that seems to address the reported vulnerability. These patches appear to have been originally committed to the common Android kernel tree on 07/29. Unfortunately, it looks like the patches have only been committed to the android-4.9, android-4.14, and android-4.19 kernels days ago (09/17) and they are not released yet or distributed by vendors. Concerned users can try to backport these commits to their own devices.

Disclosure Process

As the vulnerability is present in the Android common kernel and impacts Pixel devices we decided to disclose it to the Android Security Team on June 24. We received the first communication from them on July 24 stating they believed it was a duplicate issue. We made an inquiry about the severity, the original submission date, the technical context and the expected patch date of the reported vulnerability. We also signaled if no expected remediation date is provided we treat the report as “wont fix” and proceed with the disclosure according to our own policy. Google refused to communicate any of these details besides the original (High) severity rating. They stated that “We do not grant or deny permission to disclose”, however they would like to read the draft before publication to suggest feedback.

We believe that coordinated disclosure requires transparency and willingness to cooperate and communicate from both parties. If a vendor refuses to do so, that puts researchers in a very awkward position, where a judgement call needs to be made about the public disclosure of zero-day vulnerabilities. We decided to follow the philosophy and industry standard set by Google’s very own P0 team (read their policy here). According to their rationale, sharing technical details benefits the public in this case.

Furthermore, a manifestation of this bug has already been reported to the Android Security Team in July 2020. The reported vulnerability was point fixed in the January 2021 security bulletin and the technical details were publicly disclosed in March 2021. Just because the vendor did not carry out a thorough root cause analysis when creating a fix for the original issue, we should not assume that adversaries did not either.

Timeline

  • 2021.06.24. Vulnerability reported to the Android Security Team A-191884674
  • 2021.07.24. Google signaled they believed it was a duplicate issue of A-187527909 (this report is restricted for us)
  • 2021.07.29. Requested further information about the original report (severity, report date, technical context) and the expected remediation date
  • 2021.07.30. Google refused to provide any information besides the original severity, which was High, but confirmed that they do not deny permission to disclose
  • 2021.09.17. Patch commits appeared in Android common 4.9, 4.14, 4.19 public sources
  • 2021.09.22. Report was released publicly
  • 2021.11.01. November Android security bulletin published, includes A-187527909 as CVE-2021-0929. The issue remains restricted in the issue tracker.