Qualcomm Securitybulletin CVE-2023-33029 漏洞分析

0x01 漏洞信息

description : Memory corruption in DSP Service during a remote call from HLOS to DSP.

根据漏洞描述来看,内存损坏是在 DSP 服务中发生的,具体的触发过程是 HLOS(主机操作系统)向DSP 发送请求并等待相应的过程中。

什么是DSP ?
全称是 Digital Signal Processor , DSP 是一种专门设计来处理数字信号的微处理器。一般在信号处理领域,如图像处理、语音识别等。

什么是HLOS?
HLOS (Host Operating System): 这通常指的是运行在设备主处理器上的操作系统,它负责管理和控制设备的硬件资源,并提供用户界面和服务。

代码库路径:
https://git.codelinaro.org/clo/la/kernel/msm-5.4/-/commit/d4b9e0d3bfcb5213e23f5642cb8dcc1433542303#7b5ee30919fc6e80fe7597966e29bdc13d262367

根据其修复的日志信息,可以分析出:

  1. Thread T1 add buffer to fl->cached_bufs and release fl->hlock and holding buffer reference.
    • 线程T1将一个缓冲区(buffer)添加到fl->cached_bufs(可能是一个缓存缓冲区列表)中,然后释放fl->hlock(可能是一个互斥锁),同时保持对缓冲区的引用。
  2. Now thread T2 will acquire fl->hlock and free buffer in fastrpc_cached_buf_list_free().
    • 现在,线程T2将获取fl->hlock,然后在fastrpc_cached_buf_list_free()函数中释放缓冲区。
  3. T1 will dereference the freed buffer.
    • 线程T1将对已释放的缓冲区进行解引用(dereference)。
  4. Moving reference buffer uses for T1 inside fl->hlock to avoid UAF.
    • 为了避免使用后释放(UAF,Use After Free)的错误,建议在fl->hlock内部为T1移动引用缓冲区。

0x02 漏洞相关函数

存在漏洞的函数fastrpc_buf_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static void fastrpc_buf_free(struct fastrpc_buf *buf, int cache)
{
struct fastrpc_file *fl = buf == NULL ? NULL : buf->fl;
int vmid, err = 0, cid = -1;
if (!fl)
return;
if (buf->in_use) {
/* Don't free persistent header buf. Just mark as available */
spin_lock(&fl->hlock);
buf->in_use = false;
spin_unlock(&fl->hlock);
return;
}
if (cache && buf->size < MAX_CACHE_BUF_SIZE) {
spin_lock(&fl->hlock);
if (fl->num_cached_buf > MAX_CACHED_BUFS) {
spin_unlock(&fl->hlock);
goto skip_buf_cache;
}
hlist_add_head(&buf->hn, &fl->cached_bufs);
fl->num_cached_buf++;
spin_unlock(&fl->hlock); // 漏洞修复前
buf->type = -1;
spin_unlock(&fl->hlock); // 漏洞修复后
return;
}
skip_buf_cache:
if (buf->type == USERHEAP_BUF) {
spin_lock(&fl->hlock);
hlist_del_init(&buf->hn_rem);
spin_unlock(&fl->hlock);
buf->raddr = 0;
}
if (!IS_ERR_OR_NULL(buf->virt)) {
int destVM[1] = {VMID_HLOS};
int destVMperm[1] = {PERM_READ | PERM_WRITE | PERM_EXEC};
VERIFY(err, fl->sctx != NULL);
if (err)
goto bail;
if (fl->sctx->smmu.cb)
buf->phys &= ~((uint64_t)fl->sctx->smmu.cb << 32);
cid = fl->cid;
VERIFY(err, VALID_FASTRPC_CID(cid));
if (err) {
err = -ECHRNG;
ADSPRPC_ERR(
"invalid channel 0x%zx set for session\n",
cid);
goto bail;
}
vmid = fl->apps->channel[cid].vmid;
if (vmid) {
int srcVM[2] = {VMID_HLOS, vmid};
int hyp_err = 0;
hyp_err = hyp_assign_phys(buf->phys,
buf_page_size(buf->size),
srcVM, 2, destVM, destVMperm, 1);
if (hyp_err) {
ADSPRPC_ERR(
"rh hyp unassign failed with %d for phys 0x%llx, size %zu\n",
hyp_err, buf->phys, buf->size);
}
}
trace_fastrpc_dma_free(cid, buf->phys, buf->size);
dma_free_attrs(fl->sctx->smmu.dev, buf->size, buf->virt,
buf->phys, buf->dma_attr);
}
bail:
kfree(buf);
}

线程二要执行的函数 fastrpc_cached_buf_list_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void fastrpc_cached_buf_list_free(struct fastrpc_file *fl)
{
struct fastrpc_buf *buf, *free;
do {
struct hlist_node *n;
free = NULL;
spin_lock(&fl->hlock);
hlist_for_each_entry_safe(buf, n, &fl->cached_bufs, hn) {
hlist_del_init(&buf->hn);
fl->num_cached_buf--;
free = buf;
break;
}
spin_unlock(&fl->hlock);
if (free)
fastrpc_buf_free(free, 0);
} while (free);
}

根据漏洞修复的情况来看,唯一的不同的是 buf->type 在修复后,移到了锁的保护中。
如果按照修复前的代码中,当前线程释放锁后,此时如果有其他的线程介入,可能会在当前线程设置buf->type 为 -1 之前,就将 buf 结构体进行释放,收回该结构体的内存区域,接着当前结构体会对 buf->type 解引用,从而造成 UAF 漏洞。

1
2
3
4
5
6
hlist_add_head(&buf->hn, &fl->cached_bufs);
fl->num_cached_buf++;
spin_unlock(&fl->hlock); // 漏洞修复前
buf->type = -1;
spin_unlock(&fl->hlock); // 漏洞修复后
return;

主要结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct fastrpc_buf {
struct hlist_node hn;
struct hlist_node hn_rem;
struct hlist_node hn_init;
struct fastrpc_file *fl;
void *virt;
uint64_t phys;
size_t size;
unsigned long dma_attr;
uintptr_t raddr;
uint32_t flags;
int type; /* One of "fastrpc_buf_type" */
bool in_use; /* Used only for persistent header buffers */
struct timespec64 buf_start_time;
struct timespec64 buf_end_time;
};

驱动的函数操作符号

1
2
3
4
5
6
static const struct file_operations fops = {
.open = fastrpc_device_open,
.release = fastrpc_device_release,
.unlocked_ioctl = fastrpc_device_ioctl,
.compat_ioctl = compat_fastrpc_device_ioctl,
};

定位驱动名称

DEVICE_NAME 的定义在 adsprpc_share.h 头文件内:


0x03 代码路径分析

线程一 的执行路径:
根据fastrpc_buf_free 函数中要到达漏洞分支中,cache 参数不能0,在代码中只能看到如下调用
fastrpc_buf_free(ctx->buf, 1) —>
context_free(struct smq_invoke_ctx *ctx) —>
fastrpc_context_list_dtor(struct fastrpc_file *fl) —>
fastrpc_file_free(struct fastrpc_file *fl) —>
fastrpc_device_release(struct inode *inode, struct file *file) —>

线程二 的执行路径:
fastrpc_cached_buf_list_free 路径分析
fastrpc_cached_buf_list_free(struct fastrpc_file *fl) —>

fastrpc_cached_buf_list_free 函数有两处调用
调用一,在 fastrpc_buf_alloc 函数中,需要进入到调用处,需要经过 IS_ERR_OR_NULL 判断指针,所以不是走到这个分支。

IS_ERR_OR_NULL是Linux内核中一个常用的宏,用于检查一个指针是否表示一个错误或者为NULL
如果ptr是错误代码指针或NULL,这个宏返回true(非零值),否则返回false(零值)

调用二,fastrpc_file_free 函数中,可以看到在这里会分别调用 fastrpc_context_list_dtor 函数和 fastrpc_cached_buf_list_free 函数。

fastrpc_file_free —->
fastrpc_file_free 函数也有两处调用
调用一, fastrpc_device_release 函数中调用,这和上面线程1 执行的路径一样了,大概是使用这个调用。,因为这个调用释放的结构体和线程1 是一致的。

调用二,fastrpc_file_list_dtor(struct fastrpc_apps *me) 函数中调用,然后fastrpc_file_list_dtor 会被
fastrpc_device_exit函数调用,这是驱动退出时要执行的函数。

0x04 漏洞触发原理

这个漏洞跟blackhat 内的NPU CVE-2023-33114 类似,都是条件竞争引发的UAF。我是先看完CVE-2023-33114 后,再分析的本漏洞。

由于漏洞是条件竞争引发的UAF,按照代码路径分析的思路,整个的条件竞争如下图所示:

  • 线程一 在执行到 fastrpc_buf_free(ctx->buf, 1) 内的 spin_unlock 对 &fl 解锁,然后立马线程二在竞争时间内获取到对 &fl 的加锁。
  • 然后线程二会调用 fastrpc_buf_free(free,0) 来直接 对buf 内存直接释放。
  • 最后线程一对 buf 结构体内的指针解引用。但由于在线程二内,已经将buf 结构体的内存空间释放了,所以对 buf ->type 进行解引用时,会操作未知的内存空间,从而导致内存损坏的问题。

Drawing 2024-05-22 10.36.14.excalidraw

分析到这里,整个漏洞的触发流程相当苛刻。

首先是漏洞触发流程的并发,需要多线程执行close 函数,这就意味着调用了close 函数后,要重新调用open函数打开驱动,并不能和ioctl 一样一直不停的调用。

另外在 线程一和线程二 的执行流程中,都会执行fastrpc_file_free 函数内的 fastrpc_context_list_dtor 函数和 fastrpc_cached_buf_list_free 函数,这两个函数会先后执行。

所以线程二在执行close 的时候,也会执行fastrpc_context_list_dtor 函数,这就意味着,线程二要先走完线程一的流程,然后线程一调用fastrpc_context_list_dtor 函数执行到fastrpc_buf_free内对 &hf-hlock 进行解锁,然后线程二才能执行到fastrpc_cached_buf_list_free 函数,走完接下来要触发的条件竞争。


Qualcomm Securitybulletin CVE-2023-33029 漏洞分析
https://tig3rhu.github.io/2024/05/26/58__Qualcomm Securitybulletin CVE-2023-33029 漏洞分析/
Author
Tig3rHu
Posted on
May 26, 2024
Licensed under