From ebf71da4b00271dec00919eae66b228c107d02a9 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Tue, 13 May 2025 09:58:23 +0530 Subject: [PATCH 1/5] FROMLIST: misc: fastrpc: Fix initial memory allocation for Audio PD memory pool The initial buffer allocated for the Audio PD memory pool is never added to the pool because pageslen is set to 0. As a result, the buffer is not registered with Audio PD and is never used, causing a memory leak. Audio PD immediately falls back to allocating memory from the remote heap since the pool starts out empty. Fix this by setting pageslen to 1 so that the initially allocated buffer is correctly registered and becomes part of the Audio PD memory pool. Link: https://lore.kernel.org/all/20260409062617.1182-2-jianping.li@oss.qualcomm.com/ Fixes: 0871561055e66 ("misc: fastrpc: Add support for audiopd") Cc: stable@kernel.org Co-developed-by: Ekansh Gupta Signed-off-by: Ekansh Gupta Signed-off-by: Jianping Li --- drivers/misc/fastrpc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 50bd47d09eeef..72f67ebf53153 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1446,7 +1446,9 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, err = PTR_ERR(name); goto err; } - + inbuf.client_id = fl->client_id; + inbuf.namelen = init.namelen; + inbuf.pageslen = 0; if (!fl->cctx->remote_heap) { err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen, &fl->cctx->remote_heap); @@ -1469,12 +1471,10 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, goto err_map; } scm_done = true; + inbuf.pageslen = 1; } } - inbuf.client_id = fl->client_id; - inbuf.namelen = init.namelen; - inbuf.pageslen = 0; fl->pd = USER_PD; args[0].ptr = (u64)(uintptr_t)&inbuf; From fe638aa3e910608f13781e170595a5096f4fefb8 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Tue, 13 May 2025 09:58:24 +0530 Subject: [PATCH 2/5] FROMLIST: misc: fastrpc: Remove buffer from list prior to unmap operation fastrpc_req_munmap_impl() is called to unmap any buffer. The buffer is getting removed from the list after it is unmapped from DSP. This can create potential race conditions if any other thread removes the entry from list while unmap operation is ongoing. Remove the entry before calling unmap operation. Link: https://lore.kernel.org/all/20260409062617.1182-3-jianping.li@oss.qualcomm.com/ Fixes: 2419e55e532de ("misc: fastrpc: add mmap/unmap support") Cc: stable@kernel.org Co-developed-by: Ekansh Gupta Signed-off-by: Ekansh Gupta Signed-off-by: Jianping Li --- drivers/misc/fastrpc.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 72f67ebf53153..ce3cad84a307d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -2007,9 +2007,6 @@ static int fastrpc_req_munmap_impl(struct fastrpc_user *fl, struct fastrpc_buf * &args[0]); if (!err) { dev_dbg(dev, "unmmap\tpt 0x%09lx OK\n", buf->raddr); - spin_lock(&fl->lock); - list_del(&buf->node); - spin_unlock(&fl->lock); fastrpc_buf_free(buf); } else { dev_err(dev, "unmmap\tpt 0x%09lx ERROR\n", buf->raddr); @@ -2023,6 +2020,7 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) struct fastrpc_buf *buf = NULL, *iter, *b; struct fastrpc_req_munmap req; struct device *dev = fl->sctx->dev; + int err; if (copy_from_user(&req, argp, sizeof(req))) return -EFAULT; @@ -2030,6 +2028,7 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) spin_lock(&fl->lock); list_for_each_entry_safe(iter, b, &fl->mmaps, node) { if ((iter->raddr == req.vaddrout) && (iter->size == req.size)) { + list_del(&iter->node); buf = iter; break; } @@ -2042,7 +2041,14 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) return -EINVAL; } - return fastrpc_req_munmap_impl(fl, buf); + err = fastrpc_req_munmap_impl(fl, buf); + if (err) { + spin_lock(&fl->lock); + list_add_tail(&buf->node, &fl->mmaps); + spin_unlock(&fl->lock); + } + + return err; } static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) @@ -2133,14 +2139,17 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) if (copy_to_user((void __user *)argp, &req, sizeof(req))) { err = -EFAULT; - goto err_assign; + goto err_copy; } dev_dbg(dev, "mmap\t\tpt 0x%09lx OK [len 0x%08llx]\n", buf->raddr, buf->size); return 0; - +err_copy: + spin_lock(&fl->lock); + list_del(&buf->node); + spin_unlock(&fl->lock); err_assign: fastrpc_req_munmap_impl(fl, buf); From 808b8d8e3404c33e2c7e0166f3039d3adaf19644 Mon Sep 17 00:00:00 2001 From: Jianping Li Date: Tue, 23 Dec 2025 15:50:59 +0800 Subject: [PATCH 3/5] FROMLIST: misc: fastrpc: Allocate entire reserved memory for Audio PD in probe Allocating and freeing Audio PD memory from userspace is unsafe because the kernel cannot reliably determine when the DSP has finished using the memory. Userspace may free buffers while they are still in use by the DSP, and remote free requests cannot be safely trusted. Allocate the entire Audio PD reserved-memory region upfront during rpmsg probe and tie its lifetime to the rpmsg channel. This avoids userspace- controlled alloc/free and ensures memory is reclaimed only when the DSP shuts down. Link: https://lore.kernel.org/all/20260409062617.1182-4-jianping.li@oss.qualcomm.com/ Signed-off-by: Jianping Li --- drivers/misc/fastrpc.c | 102 ++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index ce3cad84a307d..8f3fd0b98ea42 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -292,6 +292,8 @@ struct fastrpc_channel_ctx { struct kref refcount; /* Flag if dsp attributes are cached */ bool valid_attributes; + /* Flag if audio PD init mem was allocated */ + bool audio_init_mem; u32 dsp_attributes[FASTRPC_MAX_DSP_ATTRIBUTES]; struct fastrpc_device *secure_fdevice; struct fastrpc_device *fdevice; @@ -1417,15 +1419,16 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, struct fastrpc_init_create_static init; struct fastrpc_invoke_args *args; struct fastrpc_phy_page pages[1]; + struct fastrpc_channel_ctx *cctx = fl->cctx; char *name; int err; - bool scm_done = false; struct { int client_id; u32 namelen; u32 pageslen; } inbuf; u32 sc; + unsigned long flags; args = kzalloc_objs(*args, FASTRPC_CREATE_STATIC_PROCESS_NARGS); if (!args) @@ -1449,31 +1452,6 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, inbuf.client_id = fl->client_id; inbuf.namelen = init.namelen; inbuf.pageslen = 0; - if (!fl->cctx->remote_heap) { - err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen, - &fl->cctx->remote_heap); - if (err) - goto err_name; - - /* Map if we have any heap VMIDs associated with this ADSP Static Process. */ - if (fl->cctx->vmcount) { - u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - - err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, - (u64)fl->cctx->remote_heap->size, - &src_perms, - fl->cctx->vmperms, fl->cctx->vmcount); - if (err) { - dev_err(fl->sctx->dev, - "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", - &fl->cctx->remote_heap->dma_addr, - fl->cctx->remote_heap->size, err); - goto err_map; - } - scm_done = true; - inbuf.pageslen = 1; - } - } fl->pd = USER_PD; @@ -1485,8 +1463,25 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, args[1].length = inbuf.namelen; args[1].fd = -1; - pages[0].addr = fl->cctx->remote_heap->dma_addr; - pages[0].size = fl->cctx->remote_heap->size; + spin_lock_irqsave(&cctx->lock, flags); + if (!fl->cctx->audio_init_mem) { + if (!fl->cctx->remote_heap || + !fl->cctx->remote_heap->dma_addr || + !fl->cctx->remote_heap->size) { + spin_unlock_irqrestore(&cctx->lock, flags); + err = -ENOMEM; + goto err; + } + + pages[0].addr = fl->cctx->remote_heap->dma_addr; + pages[0].size = fl->cctx->remote_heap->size; + fl->cctx->audio_init_mem = true; + inbuf.pageslen = 1; + } else { + pages[0].addr = 0; + pages[0].size = 0; + } + spin_unlock_irqrestore(&cctx->lock, flags); args[2].ptr = (u64)(uintptr_t) pages; args[2].length = sizeof(*pages); @@ -1504,26 +1499,7 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, return 0; err_invoke: - if (fl->cctx->vmcount && scm_done) { - u64 src_perms = 0; - struct qcom_scm_vmperm dst_perms; - u32 i; - - for (i = 0; i < fl->cctx->vmcount; i++) - src_perms |= BIT(fl->cctx->vmperms[i].vmid); - - dst_perms.vmid = QCOM_SCM_VMID_HLOS; - dst_perms.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, - (u64)fl->cctx->remote_heap->size, - &src_perms, &dst_perms, 1); - if (err) - dev_err(fl->sctx->dev, "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", - &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err); - } -err_map: - fastrpc_buf_free(fl->cctx->remote_heap); -err_name: + fl->cctx->audio_init_mem = false; kfree(name); err: kfree(args); @@ -2538,7 +2514,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) } } - if (domain_id == SDSP_DOMAIN_ID) { + if (domain_id == SDSP_DOMAIN_ID || domain_id == ADSP_DOMAIN_ID) { struct resource res; u64 src_perms; @@ -2550,6 +2526,15 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) data->vmperms, data->vmcount); } + if (domain_id == ADSP_DOMAIN_ID) { + data->remote_heap = + kzalloc_obj(*data->remote_heap, GFP_KERNEL); + if (!data->remote_heap) + return -ENOMEM; + + data->remote_heap->dma_addr = res.start; + data->remote_heap->size = resource_size(&res); + } } secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); @@ -2630,6 +2615,7 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev) struct fastrpc_buf *buf, *b; struct fastrpc_user *user; unsigned long flags; + int err; /* No invocations past this point */ spin_lock_irqsave(&cctx->lock, flags); @@ -2647,8 +2633,22 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev) list_for_each_entry_safe(buf, b, &cctx->invoke_interrupted_mmaps, node) list_del(&buf->node); - if (cctx->remote_heap) - fastrpc_buf_free(cctx->remote_heap); + if (cctx->remote_heap && cctx->vmcount) { + u64 src_perms = 0; + struct qcom_scm_vmperm dst_perms; + + for (u32 i = 0; i < cctx->vmcount; i++) + src_perms |= BIT(cctx->vmperms[i].vmid); + + dst_perms.vmid = QCOM_SCM_VMID_HLOS; + dst_perms.perm = QCOM_SCM_PERM_RWX; + + err = qcom_scm_assign_mem(cctx->remote_heap->dma_addr, + cctx->remote_heap->size, &src_perms, + &dst_perms, 1); + if (!err) + fastrpc_buf_free(cctx->remote_heap); + } of_platform_depopulate(&rpdev->dev); From f900cc5b12b69b0fd9749b8fdb04015c741c5a94 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Tue, 13 May 2025 09:58:21 +0530 Subject: [PATCH 4/5] FROMLIST: misc: fastrpc: Allow fastrpc_buf_free() to accept NULL Make fastrpc_buf_free() a no-op when passed a NULL pointer, allowing callers to avoid open-coded NULL checks. Link: https://lore.kernel.org/all/20260409062617.1182-5-jianping.li@oss.qualcomm.com/ Co-developed-by: Ekansh Gupta Signed-off-by: Ekansh Gupta Signed-off-by: Jianping Li --- drivers/misc/fastrpc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 8f3fd0b98ea42..a738ded5b4cba 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -436,6 +436,9 @@ static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, static void fastrpc_buf_free(struct fastrpc_buf *buf) { + if (!buf) + return; + dma_free_coherent(buf->dev, buf->size, buf->virt, fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr)); kfree(buf); @@ -553,8 +556,7 @@ static void fastrpc_context_free(struct kref *ref) for (i = 0; i < ctx->nbufs; i++) fastrpc_map_put(ctx->maps[i]); - if (ctx->buf) - fastrpc_buf_free(ctx->buf); + fastrpc_buf_free(ctx->buf); spin_lock_irqsave(&cctx->lock, flags); idr_remove(&cctx->ctx_idr, FIELD_GET(FASTRPC_CTXID_MASK, ctx->ctxid)); @@ -1687,8 +1689,7 @@ static int fastrpc_device_release(struct inode *inode, struct file *file) list_del(&fl->user); spin_unlock_irqrestore(&cctx->lock, flags); - if (fl->init_mem) - fastrpc_buf_free(fl->init_mem); + fastrpc_buf_free(fl->init_mem); list_for_each_entry_safe(ctx, n, &fl->pending, node) { list_del(&ctx->node); From e7213c470542b4e43da9ca104f87c328b063fe2c Mon Sep 17 00:00:00 2001 From: Jianping Li Date: Tue, 24 Mar 2026 15:25:55 +0800 Subject: [PATCH 5/5] FROMLIST: misc: fastrpc: fix UAF and kernel panic during cleanup on process abort When a userspace FastRPC client is abruptly terminated, FastRPC cleanup paths can race with device and session teardown. This results in kernel panics in different release paths: - fastrpc_release() when using remote heap, originating from fastrpc_buf_free() - fastrpc_device_release() when using system heap, originating from fastrpc_free_map() In addition, fastrpc_map_put() may trigger refcount use-after-free due to concurrent cleanup without proper synchronization. The root cause is that buffer and map cleanup paths may access map and buf resources after the associated device or session has already been released. Fix this by: - Introducing mutex protection for map and buf lifetime - Serializing buffer and map cleanup against device teardown - Skipping buffer and map operations when the device is already gone These changes ensure cleanup paths are safe against unexpected process aborts and prevent use-after-free and kernel panic scenarios. Link: https://lore.kernel.org/all/20260427105310.4056-1-jianping.li@oss.qualcomm.com/ Fixes: c68cfb718c8f9 ("misc: fastrpc: Add support for context Invoke method") Cc: stable@kernel.org Signed-off-by: Jianping Li --- drivers/misc/fastrpc.c | 58 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index a738ded5b4cba..3f4cc8422963a 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -271,6 +271,8 @@ struct fastrpc_session_ctx { int sid; bool used; bool valid; + bool allocated; + struct mutex mutex; }; struct fastrpc_soc_data { @@ -355,9 +357,14 @@ static inline u64 fastrpc_sid_offset(struct fastrpc_channel_ctx *cctx, static void fastrpc_free_map(struct kref *ref) { struct fastrpc_map *map; + struct fastrpc_user *fl; map = container_of(ref, struct fastrpc_map, refcount); + fl = map->fl; + if (!fl) + return; + if (map->table) { if (map->attr & FASTRPC_ATTR_SECUREMAP) { struct qcom_scm_vmperm perm; @@ -376,10 +383,16 @@ static void fastrpc_free_map(struct kref *ref) return; } } + mutex_lock(&fl->sctx->mutex); + if (!fl->sctx->dev) { + mutex_unlock(&fl->sctx->mutex); + return; + } dma_buf_unmap_attachment_unlocked(map->attach, map->table, DMA_BIDIRECTIONAL); dma_buf_detach(map->buf, map->attach); dma_buf_put(map->buf); + mutex_unlock(&fl->sctx->mutex); } if (map->fl) { @@ -439,9 +452,18 @@ static void fastrpc_buf_free(struct fastrpc_buf *buf) if (!buf) return; - dma_free_coherent(buf->dev, buf->size, buf->virt, - fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr)); - kfree(buf); + struct fastrpc_user *fl = buf->fl; + + if (!fl) + return; + mutex_lock(&fl->sctx->mutex); + if (fl->sctx->dev) { + dma_free_coherent(buf->dev, buf->size, buf->virt, + fastrpc_ipa_to_dma_addr(buf->fl->cctx, + buf->dma_addr)); + kfree(buf); + } + mutex_unlock(&fl->sctx->mutex); } static int __fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, @@ -464,8 +486,11 @@ static int __fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf->dev = dev; buf->raddr = 0; - buf->virt = dma_alloc_coherent(dev, buf->size, &buf->dma_addr, - GFP_KERNEL); + mutex_lock(&fl->sctx->mutex); + if (fl->sctx->dev) + buf->virt = dma_alloc_coherent(dev, buf->size, &buf->dma_addr, + GFP_KERNEL); + mutex_unlock(&fl->sctx->mutex); if (!buf->virt) { mutex_destroy(&buf->lock); kfree(buf); @@ -508,6 +533,10 @@ static void fastrpc_channel_ctx_free(struct kref *ref) struct fastrpc_channel_ctx *cctx; cctx = container_of(ref, struct fastrpc_channel_ctx, refcount); + for (int i = 0; i < FASTRPC_MAX_SESSIONS; i++) { + if (cctx->session[i].allocated) + mutex_destroy(&cctx->session[i].mutex); + } kfree(cctx); } @@ -850,19 +879,29 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, goto get_err; } + mutex_lock(&fl->sctx->mutex); + if (!fl->sctx->dev) { + err = -ENODEV; + mutex_unlock(&fl->sctx->mutex); + goto attach_err; + } + map->attach = dma_buf_attach(map->buf, sess->dev); if (IS_ERR(map->attach)) { dev_err(sess->dev, "Failed to attach dmabuf\n"); err = PTR_ERR(map->attach); + mutex_unlock(&fl->sctx->mutex); goto attach_err; } table = dma_buf_map_attachment_unlocked(map->attach, DMA_BIDIRECTIONAL); if (IS_ERR(table)) { err = PTR_ERR(table); + mutex_unlock(&fl->sctx->mutex); goto map_err; } map->table = table; + mutex_unlock(&fl->sctx->mutex); if (attr & FASTRPC_ATTR_SECUREMAP) map->dma_addr = sg_phys(map->table->sgl); @@ -2350,6 +2389,8 @@ static int fastrpc_cb_probe(struct platform_device *pdev) sess->used = false; sess->valid = true; sess->dev = dev; + mutex_init(&sess->mutex); + sess->allocated = true; dev_set_drvdata(dev, sess); if (cctx->domain_id == CDSP_DOMAIN_ID) @@ -2366,6 +2407,8 @@ static int fastrpc_cb_probe(struct platform_device *pdev) break; dup_sess = &cctx->session[cctx->sesscount++]; memcpy(dup_sess, sess, sizeof(*dup_sess)); + mutex_init(&dup_sess->mutex); + dup_sess->allocated = true; } } spin_unlock_irqrestore(&cctx->lock, flags); @@ -2388,6 +2431,11 @@ static void fastrpc_cb_remove(struct platform_device *pdev) spin_lock_irqsave(&cctx->lock, flags); for (i = 0; i < FASTRPC_MAX_SESSIONS; i++) { if (cctx->session[i].sid == sess->sid) { + spin_unlock_irqrestore(&cctx->lock, flags); + mutex_lock(&cctx->session[i].mutex); + cctx->session[i].dev = NULL; + mutex_unlock(&cctx->session[i].mutex); + spin_lock_irqsave(&cctx->lock, flags); cctx->session[i].valid = false; cctx->sesscount--; }