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

Reply via email to