https://labs.taszk.io/blog/post/60_ss_ion_null_deref/

Exynos ION内核驱动程序中存在一个ION内存缓冲区类型混淆漏洞。该漏洞可能导致将未初始化的内存视为有效指针,并导致内核空指针异常。untrusted app可以利用此漏洞导致内核崩溃。

函数ion_iovmm_map用于将ion缓冲区映射到总线的io地址空间,使其可用于支持dma的外部设备,并返回此dma地址。
但是呢,这个函数里有一个获取dma地址的“快速路径“:如果传入的参数attachment里有buffer,而且这个buffer带值为ION_FLAG_PROTECTED的flag,那么就从这个buffer里取关联的、预初始化的prot->dma_addr指针。

// drivers/staging/android/ion/ion_exynos.c

dma_addr_t ion_iovmm_map(struct dma_buf_attachment *attachment,
       off_t offset, size_t size,
       enum dma_data_direction direction, int prop)
{
  struct ion_buffer *buffer = attachment->dmabuf->priv;
  dma_addr_t iova;

  // 如果这个buffer带了flag ION_FLAG_PROTECTED
  if (IS_ENABLED(CONFIG_EXYNOS_CONTENT_PATH_PROTECTION) &&
      (buffer->flags & ION_FLAG_PROTECTED)) {
    // 1. 这可能是一个未初始化的指针
    struct ion_buffer_prot_info *prot = buffer->priv_virt;

    // 2. 对其进行解引用,并返回读取的值
    iova = prot->dma_addr;
  } else {
    iova = __ion_iovmm_map(attachment, offset, size,
               direction, prop);
  }

  return iova;
}

这种优化是基于这样一种假设:要么ion分配器为受保护的缓冲区初始化buffer的priv_virt指针,要么不允许使用这个标志。这个假设对大多数堆实现来说是正确的,只有所谓的rbin堆除外。从ION_HEAP_TYPE_CUSTOM2堆分配的任何ion缓冲区都使用这个分配器。rbin堆分配器(在drivers/staging/android/ion/ion_rbin_heap.c中的ion_rbin_heap_allocate)只是简单地忽略这个标志,但它从不初始化priv_virt指针。

因此,如果将从rbin堆分配的ion缓冲区传递给ion_iovmm_map函数,它就会使用未初始化的priv_virt指针。尽管ion_iovmm_map没有直接暴露给用户空间,但内核中有不同的驱动程序将用户提供的ion缓冲区传递给这个函数。

原作者已经静态验证了这样的调用链存在于以下设备的内核中:

  • /dev/vertex10npu_memory_map -> ion_iovmm_map
  • /dev/tsmuxtsmux_ioctl -> tsmux_ioctl_m2m_map_buf -> ion_iovmm_map
  • /dev/g2d /dev/fimg2dg2d_ioctl -> … -> g2d_get_dmabuf -> ion_iovmm_map
  • /dev/dspdsp_ioctl -> … -> dsp_memory_map_buffer -> ion_iovmm_map
  • /dev/m2m1shot_scaler0m2m1shot_ioctl -> … -> m2m1shot_dma_addr_map -> ion_iovmm_map
  • /dev/jsqzjsqz_ioctl -> … -> jsqz_dma_addr_map -> ion_iovmm_map
  • ...

虽然对这些设备驱动的访问在不同程度上受到selinux的限制,但它们一起提供了相当大的攻击面。最值得注意的是/dev/dsp。这个节点具有vendor_dsp_device标签,并且暴露给相当多的非特权selinux上下文,包括不受信任的应用程序。/dev/ion设备具有ion_device selinux标签,这是限制最少的selinux类型之一,它也可供不受信任的应用程序使用。

因此,untrusted app可以从rbin堆中分配一个ion缓冲区,并将其传递给/dev/dsp设备。该设备将尝试访问未初始化的内核指针,在这种情况下,这将在内核内部导致空指针解引用,并随后导致内核panic。

总结:三星自己实现的ion_iovmm_map函数里,有两条路径来映射io内存,一个是正常的mmap,另一个fast path直接从参数里取protected buffer。按设计来说,所有ion分配器在分配protected buffer的时候,都要初始化它的priv_virt指针。但是有一个rbin堆分配器没有初始化,导致在ion_iovmm_map函数里取出来之后用的时候发生空指针解引用。

参考文献

  • https://labs.taszk.io/blog/post/60_ss_ion_null_deref/