Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues: - [Critical] Sharing DMA handle maps without taking a reference allows one invocation to unmap the memory while a concurrent invocation is still actively using it on the DSP, leading to IOMMU faults. - [High] A Time-Of-Check to Time-Of-Use (TOCTOU) race condition in `fastrpc_get_args()` allows a concurrent thread to free a borrowed DMA handle map, leading to a Host-Side Use-After-Free. - [High] A lockless Read-Modify-Write sequence on `mmap->flags` in `fastrpc_put_args()` creates a data race that can be exploited to double-free a DMA handle map. Pre-existing issues: - [High] DMA handle maps created during argument processing are permanently leaked if the fastrpc invocation fails on error paths or is interrupted by a signal. -- --- Patch [1]: [PATCH v3] misc: fastrpc: avoid duplicate DMA mappings in fastrpc_create_maps() --- commit 518577268f37fc91e27676af4609f0c0fa3363fc Author: Jianping Li <[email protected]> misc: fastrpc: avoid duplicate DMA mappings in fastrpc_create_maps() This patch changes DMA handle mappings to not take an extra reference count and tags them with FASTRPC_MAP_DMA_HANDLE. It re-validates the map in fastrpc_get_args() and modifies fastrpc_put_args() to only release maps flagged as FASTRPC_MAP_DMA_HANDLE to avoid duplicate mappings. Patch [v2]: https://lore.kernel.org/all/[email protected]/ > diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c > index f3a49384586d1..59bf33cbed5af 100644 > --- a/drivers/misc/fastrpc.c > +++ b/drivers/misc/fastrpc.c > [ ... ] > @@ -999,23 +1001,25 @@ static int fastrpc_create_maps(struct > fastrpc_invoke_ctx *ctx) > int i, err; > > for (i = 0; i < ctx->nscalars; ++i) { > + bool take_ref = i < ctx->nbufs; [Severity: Critical] By setting take_ref to false for DMA handles, does this allow concurrent invocations to share a map without incrementing its reference count? If one invocation finishes and drops the only reference in fastrpc_put_args(), won't the SMMU mapping be torn down while another concurrent invocation is still actively using it on the DSP? > + int mflags = 0; > > if (ctx->args[i].fd == 0 || ctx->args[i].fd == -1 || > ctx->args[i].length == 0) > continue; > > - if (i < ctx->nbufs) > - err = fastrpc_map_create(ctx->fl, ctx->args[i].fd, > - ctx->args[i].length, ctx->args[i].attr, > &ctx->maps[i]); > - else > - err = fastrpc_map_attach(ctx->fl, ctx->args[i].fd, > - ctx->args[i].length, ctx->args[i].attr, > &ctx->maps[i]); > + /* Set the DMA handle mapping flag for DMA handles */ > + if (i >= ctx->nbufs) > + mflags = FASTRPC_MAP_DMA_HANDLE; > + > + err = fastrpc_map_create(ctx->fl, ctx->args[i].fd, > ctx->args[i].length, > + ctx->args[i].attr, &ctx->maps[i], > take_ref, mflags); [Severity: High] This is a pre-existing issue, but does this code permanently leak DMA handles if an error occurs after map creation? If an error happens or a signal interrupts the process, the cleanup path fastrpc_context_free() intentionally omits freeing maps where i >= ctx->nbufs: drivers/misc/fastrpc.c:fastrpc_context_free() { ... for (i = 0; i < ctx->nbufs; i++) fastrpc_map_put(ctx->maps[i]); ... } Can an unprivileged user intentionally provide invalid arguments (e.g., bad payload size) to trigger a failure after map creation, causing an unbounded memory leak of orphaned DMA handles? > if (err) { > dev_err(dev, "Error Creating map %d\n", err); > return -EINVAL; > } > - > } > + > return 0; > } > [ ... ] > @@ -1143,6 +1147,16 @@ static int fastrpc_get_args(u32 kernel, struct > fastrpc_invoke_ctx *ctx) > list[i].num = ctx->args[i].length ? 1 : 0; > list[i].pgidx = i; > if (ctx->maps[i]) { > + /* It is possible that map is created with > + * mflags FASTRPC_MAP_DMA_HANDLE and take_ref > + * is false. Check if map still exists or is > + * being freed as take_ref is false > + */ > + if (fastrpc_map_lookup(ctx->fl, ctx->args[i].fd, > + &ctx->maps[i], false)) { > + ctx->maps[i] = NULL; > + return -EINVAL; > + } > pages[i].addr = ctx->maps[i]->dma_addr; [Severity: High] Is there a race condition between looking up the map and dereferencing it? Since fastrpc_map_lookup() is called with take_ref = false, it locklessly returns a pointer to the shared map without holding a reference. Could a concurrent thread finish its invocation and free this shared map before pages[i].addr = ctx->maps[i]->dma_addr is evaluated, resulting in a host-side use-after-free? > + pages[i].size = ctx->maps[i]->size; > } > [ ... ] > @@ -1199,8 +1213,17 @@ static int fastrpc_put_args(struct fastrpc_invoke_ctx > *ctx, > for (i = 0; i < FASTRPC_MAX_FDLIST; i++) { > if (!fdlist[i]) > break; > - if (!fastrpc_map_lookup(fl, (int)fdlist[i], &mmap, false)) > + /* > + * DMA handle maps are released when the DSP returns the > corresponding fd in > + * fdlist. The DSP is expected to return a specific fd only > once in fdlist, > + * so no two fastrpc_put_args() paths should clear the > DMA_HANDLE flag for > + * the same map concurrently. > + */ > + if (!fastrpc_map_lookup(fl, (int)fdlist[i], &mmap, false) && > + mmap->flags == FASTRPC_MAP_DMA_HANDLE) { > + mmap->flags = 0; > fastrpc_map_put(mmap); > + } [Severity: High] Does the lockless check of mmap->flags allow a double-free if concurrent invocations share the same fd? If two concurrent threads from the same user process share the same DMA handle fd, they share the same map object. When both invocations finish, they could execute fastrpc_put_args() concurrently. If they reach this check at the same time, couldn't they both evaluate mmap->flags == FASTRPC_MAP_DMA_HANDLE as true, clear the flag, and call fastrpc_map_put() twice on the same map? > } > > return ret; -- Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=1
