经过前面的分析,CVE-2024-33060 和 CVE-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:
- Create a small mapping A with va == 0 using fastrpc_internal_mmap
- Create context 1, get_args gets a reference to mapping A as a handle.
- Create a BIG second mapping B with va == 0 using fastrpc_internal_mmap with the same fd as mapping A.
- Create context 2, grab a reference to mapping B as a buffer (vs a handle) so that we use the ctx->maps pointer.
- 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.
- 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