fastrpc_mmap_find 中的搜索算法不正确,导致内核地址空间信息泄露。
这个洞也被 CVE-2024-33060 的patch给修复了,因此共享同一个cve

1 漏洞

fastrpc_mmap_create 函数使用攻击者可控的参数调用 fastrpc_mmap_find,以查找是否存在已满足映射创建请求的现有映射。fastrpc_mmap_find函数如果找到满足的map,则返回0,否则返回-ENXIO

static int fastrpc_mmap_create(struct fastrpc_file *fl, int fd,
	unsigned int attr, uintptr_t va, size_t len, int mflags,
	struct fastrpc_mmap **ppmap)
{
	......
	chan = &apps->channel[cid];
	if (!fastrpc_mmap_find(fl, fd, va, len, mflags, 1, ppmap))
		return 0;
	......
}

如果找到满足请求的映射,代码就会对该映射的引用计数进行加一,然后直接返回。具体的查找过程里,会根据mflags来选择在全局还是本地的映射里去寻找:

  • 当在本地映射里找时(31行),map->va 被设置成用户态提供的地址,这段代码是合乎逻辑的。
  • 但对于全局映射(11行),其 map->va 实际上是一个内核的 struct page 指针,用来充当所分配内存的opaque cookie(不透明句柄,即不该让用户知道)。
    • map->va的来源是什么?
static int fastrpc_mmap_find(struct fastrpc_file *fl, int fd,
		uintptr_t va, size_t len, int mflags, int refs,
		struct fastrpc_mmap **ppmap)
{
	struct fastrpc_apps *me = &gfa;
	struct fastrpc_mmap *match = NULL, *map = NULL;
	struct hlist_node *n;
	if ((va + len) < va)
		return -EFAULT;
	// 全局映射里找
	if (mflags == ADSP_MMAP_HEAP_ADDR ||
				 mflags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
		spin_lock(&me->hlock);
		hlist_for_each_entry_safe(map, n, &me->maps, hn) {
			if (va >= map->va &&
				va + len <= map->va + map->len &&
				map->fd == fd) { // 用户态提供的 va 和 len 是否在映射范围内,以及fd是否相同
				if (refs) {
					if (map->refs + 1 == INT_MAX) {
						spin_unlock(&me->hlock);
						return -ETOOMANYREFS;
					}
					map->refs++;
				}
				match = map;
				break;
			}
		}
		spin_unlock(&me->hlock);
	// 本地映射里找
	} else {
		hlist_for_each_entry_safe(map, n, &fl->maps, hn) {
			if (va >= map->va &&
				va + len <= map->va + map->len &&
				map->fd == fd) {
				if (refs) {
					if (map->refs + 1 == INT_MAX)
						return -ETOOMANYREFS;
					map->refs++;
				}
				match = map;
				break;
			}
		}
	}
	......
}

当是全局映射时,即va是堆地址(ADSP_MMAP_REMOTE_HEAP_ADDR),map->va 的值来源于 fastrpc_alloc_cma_memory 中调用的 dma_alloc_attrs 函数。

该函数有两种工作模式:

  1. 返回内核虚拟地址:指向分配的内存。
  2. 返回 struct page 指针:作为内存的“不透明句柄”(opaque cookie)。

当是全局映射时,dma_alloc_attrs 函数是第二种工作模式。有两个依据,第一个是代码,如下所示。
创建map时,fastrpc_mmap_create先设置map的attr等(重点是DMA_ATTR_NO_KERNEL_MAPPING,这涉及哪种工作模式),然后调用函数fastrpc_alloc_cma_memory去分配cma内存。

// fastrpc_mmap_device_ioctl ->
// case FASTRPC_IOCTL_MEM_MAP ->
// fastrpc_internal_mem_map ->
fastrpc_mmap_create{ // 
	......
	if (mflags == ADSP_MMAP_HEAP_ADDR ||
				mflags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
		map->apps = me;
		map->fl = NULL;
		map->attr |= DMA_ATTR_SKIP_ZEROING | DMA_ATTR_NO_KERNEL_MAPPING;
		err = fastrpc_alloc_cma_memory(&region_phys, &region_vaddr,
					len, (unsigned long) map->attr);
				}
	......
}

函数fastrpc_alloc_cma_memory调用dma_alloc_attrs,并传入前面设置的attr。

static int fastrpc_alloc_cma_memory(dma_addr_t *region_phys, void **vaddr, size_t size, unsigned long dma_attr)
	......
	*vaddr = dma_alloc_attrs(me->dev, size, region_phys, GFP_KERNEL, dma_attr);
	......
}

继续往下调用dma_direct_alloc

void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag, unsigned long attrs)
{
	......
	if (dma_is_direct(ops))
		cpu_addr = dma_direct_alloc(dev, size, dma_handle, flag, attrs);
	......
}

而函数dma_direct_alloc中,如果attr指定了 DMA_ATTR_NO_KERNEL_MAPPING,则不会返回内核虚拟地址,而是返回一个 struct page 指针

void *dma_direct_alloc_pages(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs)
{
	......
	page = __dma_direct_alloc_pages(dev, size, dma_handle, gfp, attrs);
	......
	if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) && // 如果attr指定了 DMA_ATTR_NO_KERNEL_MAPPING
		!force_dma_unencrypted(dev)) {
		/* remove any dirty cache lines on the kernel alias */
		if (!PageHighMem(page))
			arch_dma_prep_coherent(page, size);
		*dma_handle = phys_to_dma(dev, page_to_phys(page)); // 返回一个page指针作为 opaque cookie
		/* return the page pointer as the opaque cookie */
		return page;
	}
}

此外,我们看一下创建本地映射时map->va的赋值是怎样的:

fastrpc_mmap_create{ // 
	......
	if (mflags == ADSP_MMAP_HEAP_ADDR ||
				mflags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
		......
	} else if (mflags == FASTRPC_DMAHANDLE_NOMAP) {
		......
		map->va = 0; // 设置为0
		......
	}else {
		......
		map->va = va; // 设置为用户态传入的va
		......
	}
	

第二个依据是通过 debugfs 查看 adsprpc 目录下的全局映射文件(GMAPS)。

https://project-zero.issues.chromium.org/issues/42451713

===================================  GMAPS  ====================================    
fd                  |phys                |size                |va                      
--------------------------------------------------------------------------------    
-1                  |0xE883A000          |0x1000              |0xFFFFFFFE01A20E80        
   
-1                  |0xE8839000          |0x1000              |0xFFFFFFFE01A20E40        
   
-1                  |0xE8838000          |0x1000              |0xFFFFFFFE01A20E00        
   
-1                  |0xE8837000          |0x1000              |0xFFFFFFFE01A20DC0        
   
-1                  |0xE8836000          |0x1000              |0xFFFFFFFE01A20D80        
   
-1                  |0xE8835000          |0x1000              |0xFFFFFFFE01A20D40        
   
0                   |0xE8834000          |0x1000              |0xFFFFFFFE01A20D00        
   
0                   |0xE8833000          |0x1000              |0xFFFFFFFE01A20CC0        
   
0                   |0xE8832000          |0x1000              |0xFFFFFFFE01A20C80        
   
-1                  |0xE8900000          |0x200000            |0xFFFFFFFE01A24000

这里的 va 字段实际上是struct page指针,而非真实内存地址。(物理地址的 offset 为一页,而对应 va 的 offset 则为0x40 -- page结构体的size)。

因此,函数fastrpc_mmap_find中,用户态提供的 va 值会被直接与内核的 struct page 指针比较。同时由于 ioctl 的返回值会根据比较结果(成功(0)/-ENXIO/-ETOOMANYREFS)不同,攻击者可以对与 fastrpc_map 对象相关联的 struct page 指针进行暴力猜测(brute force),导致内核地址信息的泄露。

如 sethjenkins 在三星 S23 上运行 PoC 的输出如下:

dm1q:/data/local/tmp $ ./poc
Detected address 0xfffffffe01c00000
Final address: 0xfffffffe01a24000

此外,由于 map->va 是 struct page 指针而非实际内存地址,代码中直接使用 map->va + map->len 进行范围判断是错误的。这可能导致同一调用参数匹配到多个映射对象,进一步引发逻辑混乱。

2 Patch

和 CVE-2024-33060 是同一个patch链接,find函数里只搜索 local map,不再搜索global map。

参考文献

  • https://googleprojectzero.blogspot.com/2024/12/qualcomm-dsp-driver-unexpectedly-excavating-exploit.html
  • https://project-zero.issues.chromium.org/issues/42451713
  • https://git.codelinaro.org/clo/la/kernel/msm-5.4/-/commit/4056f87e3b347e0283234f56b9e9aaea681d1644