There is a race between fastrpc_device_release() and the workqueue
that processes DSP responses. When the user closes the file descriptor,
fastrpc_device_release() frees the fastrpc_user structure. Concurrently,
an in-flight DSP invocation can complete and fastrpc_rpmsg_callback()
schedules context cleanup via schedule_work(&ctx->put_work). If the
workqueue runs fastrpc_context_free() in parallel with or after
fastrpc_device_release() has freed the user structure, it dereferences
the freed fastrpc_user. Depending on the state of the context at the
time of the race, any one of the following accesses can be hit:

 1. fastrpc_buf_free() calls fastrpc_ipa_to_dma_addr(buf->fl->cctx, ...)
    to strip the SID bits from the stored IOVA before passing the
    physical address to dma_free_coherent().

 2. fastrpc_free_map() reads map->fl->cctx->vmperms[0].vmid to
    reconstruct the source permission bitmask needed for the
    qcom_scm_assign_mem() call that returns memory from the DSP VM
    back to HLOS.

 3. fastrpc_free_map() acquires map->fl->lock to safely remove the
    map node from the fl->maps list.

The resulting use-after-free manifests as:

  pc : fastrpc_buf_free+0x38/0x80 [fastrpc]
  lr : fastrpc_context_free+0xa8/0x1b0 [fastrpc]
  fastrpc_context_free+0xa8/0x1b0 [fastrpc]
  fastrpc_context_put_wq+0x78/0xa0 [fastrpc]
  process_one_work+0x180/0x450
  worker_thread+0x26c/0x388

Add kref-based reference counting to fastrpc_user. Have each invoke
context take a reference on the user at allocation time and release it
when the context is freed. Release the initial reference in
fastrpc_device_release() at file close. Move the teardown of the user
structure — freeing pending contexts, maps, mmaps, and the channel
context reference — into the kref release callback fastrpc_user_free(),
so that it runs only when the last reference is dropped, regardless of
whether that happens at device close or after the final in-flight
context completes.

Fixes: 6cffd79504ce ("misc: fastrpc: Add support for dmabuf exporter")
Cc: [email protected]
Signed-off-by: Anandu Krishnan E <[email protected]>
---
Changes in v4:
 - Fixed a blank line issue
 - Link to v3: 
https://lore.kernel.org/all/[email protected]/

Changes in v3:
 - Fixed fastrpc_user_put()/fastrpc_channel_ctx_put() call order in
   fastrpc_context_free() and the err_idr path of fastrpc_context_alloc();
   the correct ordering from v1 was accidentally reversed in v2
 - Link to v2: 
https://lore.kernel.org/all/[email protected]/

Changes in v2:
 - Rewrote commit message to establish the problem first per review
   feedback; identified all three UAF dereference sites explicitly
 - Moved resource cleanup (pending contexts, maps, mmaps) into
   fastrpc_user_free() so teardown is consolidated in the kref
   release callback
 - Link to v1: 
https://lore.kernel.org/all/[email protected]/

 drivers/misc/fastrpc.c | 75 +++++++++++++++++++++++++++++-------------
 1 file changed, 52 insertions(+), 23 deletions(-)

diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c
index 1080f9acf70a..48f8262af539 100644
--- a/drivers/misc/fastrpc.c
+++ b/drivers/misc/fastrpc.c
@@ -310,6 +310,8 @@ struct fastrpc_user {
        spinlock_t lock;
        /* lock for allocations */
        struct mutex mutex;
+       /* Reference count */
+       struct kref refcount;
 };
 
 /* Extract SMMU PA from consolidated IOVA */
@@ -497,15 +499,57 @@ static void fastrpc_channel_ctx_put(struct 
fastrpc_channel_ctx *cctx)
        kref_put(&cctx->refcount, fastrpc_channel_ctx_free);
 }
 
+static void fastrpc_context_put(struct fastrpc_invoke_ctx *ctx);
+
+static void fastrpc_user_free(struct kref *ref)
+{
+       struct fastrpc_user *fl = container_of(ref, struct fastrpc_user, 
refcount);
+       struct fastrpc_invoke_ctx *ctx, *n;
+       struct fastrpc_map *map, *m;
+       struct fastrpc_buf *buf, *b;
+
+       if (fl->init_mem)
+               fastrpc_buf_free(fl->init_mem);
+
+       list_for_each_entry_safe(ctx, n, &fl->pending, node) {
+               list_del(&ctx->node);
+               fastrpc_context_put(ctx);
+       }
+
+       list_for_each_entry_safe(map, m, &fl->maps, node)
+               fastrpc_map_put(map);
+
+       list_for_each_entry_safe(buf, b, &fl->mmaps, node) {
+               list_del(&buf->node);
+               fastrpc_buf_free(buf);
+       }
+
+       fastrpc_channel_ctx_put(fl->cctx);
+       mutex_destroy(&fl->mutex);
+       kfree(fl);
+}
+
+static void fastrpc_user_get(struct fastrpc_user *fl)
+{
+       kref_get(&fl->refcount);
+}
+
+static void fastrpc_user_put(struct fastrpc_user *fl)
+{
+       kref_put(&fl->refcount, fastrpc_user_free);
+}
+
 static void fastrpc_context_free(struct kref *ref)
 {
        struct fastrpc_invoke_ctx *ctx;
        struct fastrpc_channel_ctx *cctx;
+       struct fastrpc_user *fl;
        unsigned long flags;
        int i;
 
        ctx = container_of(ref, struct fastrpc_invoke_ctx, refcount);
        cctx = ctx->cctx;
+       fl = ctx->fl;
 
        for (i = 0; i < ctx->nbufs; i++)
                fastrpc_map_put(ctx->maps[i]);
@@ -521,6 +565,8 @@ static void fastrpc_context_free(struct kref *ref)
        kfree(ctx->olaps);
        kfree(ctx);
 
+       /* Release the reference taken in fastrpc_context_alloc() */
+       fastrpc_user_put(fl);
        fastrpc_channel_ctx_put(cctx);
 }
 
@@ -628,6 +674,8 @@ static struct fastrpc_invoke_ctx *fastrpc_context_alloc(
 
        /* Released in fastrpc_context_put() */
        fastrpc_channel_ctx_get(cctx);
+       /* Take a reference to user, released in fastrpc_context_free() */
+       fastrpc_user_get(user);
 
        ctx->sc = sc;
        ctx->retval = -1;
@@ -658,6 +706,7 @@ static struct fastrpc_invoke_ctx *fastrpc_context_alloc(
        spin_lock(&user->lock);
        list_del(&ctx->node);
        spin_unlock(&user->lock);
+       fastrpc_user_put(user);
        fastrpc_channel_ctx_put(cctx);
        kfree(ctx->maps);
        kfree(ctx->olaps);
@@ -1579,9 +1628,6 @@ static int fastrpc_device_release(struct inode *inode, 
struct file *file)
 {
        struct fastrpc_user *fl = (struct fastrpc_user *)file->private_data;
        struct fastrpc_channel_ctx *cctx = fl->cctx;
-       struct fastrpc_invoke_ctx *ctx, *n;
-       struct fastrpc_map *map, *m;
-       struct fastrpc_buf *buf, *b;
        unsigned long flags;
 
        fastrpc_release_current_dsp_process(fl);
@@ -1590,28 +1636,10 @@ 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);
-
-       list_for_each_entry_safe(ctx, n, &fl->pending, node) {
-               list_del(&ctx->node);
-               fastrpc_context_put(ctx);
-       }
-
-       list_for_each_entry_safe(map, m, &fl->maps, node)
-               fastrpc_map_put(map);
-
-       list_for_each_entry_safe(buf, b, &fl->mmaps, node) {
-               list_del(&buf->node);
-               fastrpc_buf_free(buf);
-       }
-
        fastrpc_session_free(cctx, fl->sctx);
-       fastrpc_channel_ctx_put(cctx);
-
-       mutex_destroy(&fl->mutex);
-       kfree(fl);
        file->private_data = NULL;
+       /* Release the reference taken in fastrpc_device_open */
+       fastrpc_user_put(fl);
 
        return 0;
 }
@@ -1655,6 +1683,7 @@ static int fastrpc_device_open(struct inode *inode, 
struct file *filp)
        spin_lock_irqsave(&cctx->lock, flags);
        list_add_tail(&fl->user, &cctx->users);
        spin_unlock_irqrestore(&cctx->lock, flags);
+       kref_init(&fl->refcount);
 
        return 0;
 }
-- 
2.34.1

Reply via email to