mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
misc: fastrpc: fix use-after-free of fastrpc_user in workqueue context
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: 6cffd79504 ("misc: fastrpc: Add support for dmabuf exporter")
Cc: stable@kernel.org
Signed-off-by: Anandu Krishnan E <anandu.e@oss.qualcomm.com>
Signed-off-by: Srinivas Kandagatla <srini@kernel.org>
Link: https://patch.msgid.link/20260530204528.116920-2-srini@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
55f2ea9ff8
commit
e85eb5feca
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user