经过前面的分析,CVE-2024-33060CVE-2024-49848 极有可能是攻击者实际在野利用的漏洞。但有两个矛盾点

  • 若漏洞触发的是 CVE-2024-33060,理论上应产生某些内核日志,但实际攻击样本(ITW artifacts)中未见此类记录。
  • CVE-2024-49848 的影响范围仅限于更新版本的内核,与攻击目标内核版本不匹配。
    所以,P0专家继续审计,发现CVE-2024-43047。

漏洞

context对map引用的处理方式取决于该映射是作为缓冲区(buffer)还是句柄(handle)使用。context通过一个动态分配的数组 ctx->maps 来存储映射指针的引用,该数组同时包含缓冲区和句柄的引用。

// 获取内核调用参数并管理映射引用
static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx) {
    ...
    uint32_t sc = ctx->sc;
    int inbufs = REMOTE_SCALARS_INBUFS(sc); 
    int outbufs = REMOTE_SCALARS_OUTBUFS(sc);
    int handles, bufs = inbufs + outbufs;
    
    // 第一阶段:处理缓冲区(buffers)引用
    for (i = 0; i < bufs; ++i) { 
        ...
        if (ctx->fds && (ctx->fds[i] != -1))
            // 创建映射并关联到 ctx->maps[i]
            err = fastrpc_mmap_create(ctx->fl, ctx->fds[i], NULL, 
                            ctx->attrs[i], buf, len, mflags, &ctx->maps[i]);
        if (ctx->maps[i]) 
            ctx->maps[i]->ctx_refs++; // 引用计数递增
        ...
    }
    
    // 第二阶段:处理句柄(handles)引用
    handles = REMOTE_SCALARS_INHANDLES(sc) + REMOTE_SCALARS_OUTHANDLES(sc);
    for (i = bufs; i < bufs + handles; i++) { 
        ...
        
        if (!dsp_cap_ptr->dsp_attributes[DMA_HANDLE_REVERSE_RPC_CAP] && 
            ctx->fds && (ctx->fds[i] != -1)) 
            // 创建DMA句柄映射(属性强制为FASTRPC_ATTR_NOVA)
            err = fastrpc_mmap_create(ctx->fl, ctx->fds[i], NULL, 
                            FASTRPC_ATTR_NOVA, 0, 0, dmaflags, &ctx->maps[i]);
        
        // 无论是否创建成功,均尝试递增引用计数
        if (!err && ctx->maps[i]) 
            ctx->maps[i]->ctx_refs++; 
        
        // 错误处理:仅回滚句柄部分的映射
        if (err) {
            for (j = bufs; j < i; j++) {  // 注意:j从bufs开始,未覆盖缓冲区映射!
                if (ctx->maps[j] && ctx->maps[j]->ctx_refs) 
                    ctx->maps[j]->ctx_refs--; 
                fastrpc_mmap_free(ctx->maps[j], 0); // 释放映射
            }
            ...
        }
        ...
    }
    mutex_unlock(&ctx->fl->map_mutex);
}

然而,只有缓冲区(buffers)会通过 ctx->maps 中的指针引用被释放。实际上,一旦句柄映射(handle maps)被成功初始化,其在 ctx->maps 中的引用便不再被使用。相反,DSP会获取与映射文件描述符关联的值的列表,并在使用完成后将这些文件描述符传回AP的 adsprpc 驱动。随后,AP 会查找与 DSP 返回的文件描述符关联的映射,并减少其引用计数,最终可能释放该映射:

static int put_args(uint32_t kernel, struct smq_invoke_ctx *ctx,
                    remote_arg_t *upra) {
    ...
    for (i = 0; i < M_FDLIST; i++) {
        if (!fdlist[i])
            break;
        if (!fastrpc_mmap_find(ctx->fl, (int)fdlist[i], NULL, 0, 0, 0, 0, &mmap)) {
            if (mmap && mmap->ctx_refs)
                mmap->ctx_refs--;
            fastrpc_mmap_free(mmap, 0);
        }
    }
    ...
}

此逻辑存在缺陷,因为无法保证 put_args 中找到并减少引用计数的映射与 get_args 先前引用的映射是同一个。实际上,它可能是另一个上下文中仍作为缓冲区引用的映射。

当存在映射碰撞(多个映射满足 fastrpc_mmap_find 的搜索条件)时,此漏洞将被触发。由于 fastrpc_mmap_find 会返回包含目标虚拟地址范围的任何映射,攻击者可以通过创建范围覆盖现有映射的“超集映射”来引发碰撞。例如:mapB->va == mapA->va && mapB->len > mapA->len

An example of this map collision that leads to memory corruption is:

  1. Create a small mapping A with va == 0 using fastrpc_internal_mmap
  2. Create context 1, get_args gets a reference to mapping A as a handle.
  3. Create a BIG second mapping B with va == 0 using fastrpc_internal_mmap with the same fd as mapping A.
  4. Create context 2, grab a reference to mapping B as a buffer (vs a handle) so that we use the ctx->maps pointer.
  5. Complete context 1, causing put_args to be called. We find and drop a refcount on mapping B since it collides with mapping A. Mapping A’s refcount is permanently leaked.
  6. We then unmap mapping B using FASTRPC_IOCTL_MUNMAP

Now there is a still valid context (context 2 in the above example) that still has a reference to mapping B even though mapping B was freed.

1. 创建映射碰撞
ctx1 -- mapping A (va=0, ctx_ref+1)
ctx2 -- mapping B  (va=0, 范围更大, ctx_ref+1)

2. 错误put
ctx1 put_args,错误找到 mapping B,并ctx_ref--(此时mapping A的引用计数永久泄漏,无法再被释放)

3. 释放
unmap mapping B 释放

4. uaf
ctx2 仍持有mapping B的引用,UAF

PoC

猜测是因为在野利用,没公开

Patch

参考文献

  • https://googleprojectzero.blogspot.com/2024/12/qualcomm-dsp-driver-unexpectedly-excavating-exploit.html