adsprpc: Handle UAF scenario in put_args

Currently, the DSP updates header buffers with unused DMA handle fds.
In the put_args section, if any DMA handle FDs are present in the
header buffer, the corresponding map is freed. However, since the
header buffer is exposed to users in unsigned PD, users can update
invalid FDs. If this invalid FD matches with any FD that is already
in use, it could lead to a use-after-free (UAF) vulnerability.
As a solution,add DMA handle references for DMA FDs, and the map for
the FD will be freed only when a reference is found.

Acked-by: Om Deore <quic_odeore@quicinc.com>
Change-Id: I3c2614451f7b3717236708ee5e9b88f16f6e435d
Signed-off-by: Santosh <quic_ssakore@quicinc.com>
This commit is contained in:
Santosh
2024-08-22 17:52:33 +05:30
committed by Santosh Sakore
parent 09012c0af4
commit 71034c852d

View File

@@ -424,6 +424,8 @@ struct fastrpc_mmap {
uintptr_t attr;
bool is_filemap; /* flag to indicate map used in process init */
unsigned int ctx_refs; /* Indicates reference count for context map */
/* Map in use for dma handle */
unsigned int dma_handle_refs;
};
enum fastrpc_perfkeys {
@@ -850,8 +852,12 @@ static int fastrpc_mmap_remove(struct fastrpc_file *fl, uintptr_t va,
}
hlist_for_each_entry_safe(map, n, &fl->maps, hn) {
/* Remove if only one reference map and no context map */
if (map->refs == 1 && !map->ctx_refs &&
map->raddr == va && map->raddr + map->len == va + len &&
if (map->refs == 1 &&
!map->ctx_refs &&
map->raddr == va &&
map->raddr + map->len == va + len &&
/* Remove map only if it isn't being used by DSP */
!map->dma_handle_refs &&
/* Remove map if not used in process initialization */
!map->is_filemap) {
match = map;
@@ -890,15 +896,21 @@ static void fastrpc_mmap_free(struct fastrpc_mmap *map, uint32_t flags)
if (map->flags == ADSP_MMAP_HEAP_ADDR ||
map->flags == ADSP_MMAP_REMOTE_HEAP_ADDR) {
spin_lock(&me->hlock);
map->refs--;
if (!map->refs && !map->ctx_refs)
if (map->refs)
map->refs--;
if (!map->refs)
hlist_del_init(&map->hn);
spin_unlock(&me->hlock);
if (map->refs > 0)
return;
} else {
map->refs--;
if (!map->refs && !map->ctx_refs)
if (map->refs)
map->refs--;
/* flags is passed as 1 during fastrpc_file_free
* (ie process exit), so that maps will be cleared
* even though references are present.
*/
if (!map->refs && !map->ctx_refs && !map->dma_handle_refs)
hlist_del_init(&map->hn);
if (map->refs > 0 && !flags)
return;
@@ -1770,12 +1782,14 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx)
FASTRPC_ATTR_NOVA, 0, 0, dmaflags,
&ctx->maps[i]);
if (!err && ctx->maps[i])
ctx->maps[i]->ctx_refs++;
ctx->maps[i]->dma_handle_refs++;
if (err) {
for (j = bufs; j < i; j++) {
if (ctx->maps[j] && ctx->maps[j]->ctx_refs)
ctx->maps[j]->ctx_refs--;
fastrpc_mmap_free(ctx->maps[j], 0);
if (ctx->maps[j] &&
ctx->maps[j]->dma_handle_refs) {
ctx->maps[j]->dma_handle_refs--;
fastrpc_mmap_free(ctx->maps[j], 0);
}
}
mutex_unlock(&ctx->fl->map_mutex);
goto bail;
@@ -1884,13 +1898,33 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx)
rpra[i].buf.pv = buf;
}
PERF_END);
/* Since we are not holidng map_mutex during get args whole time
* it is possible that dma handle map may be removed by some invalid
* fd passed by DSP. Inside the lock check if the map present or not
*/
mutex_lock(&ctx->fl->map_mutex);
for (i = bufs; i < bufs + handles; ++i) {
struct fastrpc_mmap *map = ctx->maps[i];
if (map) {
pages[i].addr = map->phys;
pages[i].size = map->size;
struct fastrpc_mmap *mmap = NULL;
/* check if map was created */
if (ctx->maps[i]) {
/* check if map still exist */
if (!fastrpc_mmap_find(ctx->fl, ctx->fds[i], 0, 0,
0, 0, &mmap)) {
if (mmap) {
pages[i].addr = mmap->phys;
pages[i].size = mmap->size;
}
} else {
/* map already freed by some other call */
mutex_unlock(&ctx->fl->map_mutex);
pr_err("could not find map associated with dma handle fd %d\n",
ctx->fds[i]);
goto bail;
}
}
}
mutex_unlock(&ctx->fl->map_mutex);
fdlist = (uint64_t *)&pages[bufs + handles];
crclist = (uint32_t *)&fdlist[M_FDLIST];
/* reset fds, crc and early wakeup hint memory */
@@ -2073,9 +2107,10 @@ static int put_args(uint32_t kernel, struct smq_invoke_ctx *ctx,
break;
if (!fastrpc_mmap_find(ctx->fl, (int)fdlist[i], 0, 0,
0, 0, &mmap)) {
if (mmap && mmap->ctx_refs)
mmap->ctx_refs--;
fastrpc_mmap_free(mmap, 0);
if (mmap && mmap->dma_handle_refs) {
mmap->dma_handle_refs = 0;
fastrpc_mmap_free(mmap, 0);
}
}
}
}