[In-The-Wild] CVE-2024-43047 : Qualcomm DSP Service Use-After-Free lead to Elevation of Privilege Vulnerability
Summary:
- CVE ID : CVE-2024-43047
- Target Software : Qualcomm DSP Service
- Affected Version : FastConnect 6700, FastConnect 6800, FastConnect 6900, FastConnect 7800, QAM8295P, QCA6174A, QCA6391, QCA6426, QCA6436, QCA6574AU, QCA6584AU, QCA6595, QCA6595AU, QCA6688AQ, QCA6696, QCA6698AQ, QCS410, QCS610, QCS6490, Qualcomm® Video Collaboration VC1 Platform, Qualcomm® Video Collaboration VC3 Platform, SA4150P, SA4155P, SA6145P, SA6150P, SA6155P, SA8145P, SA8150P, SA8155P, SA8195P, SA8295P, SD660, SD865 5G, SG4150P, Snapdragon 660 Mobile Platform, Snapdragon 680 4G Mobile Platform, Snapdragon 685 4G Mobile Platform (SM6225-AD), Snapdragon 8 Gen 1 Mobile Platform, Snapdragon 865 5G Mobile Platform, Snapdragon 865+ 5G Mobile Platform (SM8250-AB), Snapdragon 870 5G Mobile Platform (SM8250-AC), Snapdragon 888 5G Mobile Platform, Snapdragon 888+ 5G Mobile Platform (SM8350-AC), Snapdragon Auto 5G Modem-RF, Snapdragon Auto 5G Modem-RF Gen 2, Snapdragon X55 5G Modem-RF System, Snapdragon XR2 5G Platform, SW5100, SW5100P, SXR2130, WCD9335, WCD9341, WCD9370, WCD9375, WCD9380, WCD9385, WCN3950, WCN3980, WCN3988, WCN3990, WSA8810, WSA8815, WSA8830, WSA8835
- Patched Version : Qualcomm October 2024 Security Bulletin
- Impact : Elevation of Privilege
- Reporter(s) : Seth Jenkins of Google Project Zero & Conghui Wang & Amnesty International Security Lab confirming in-the-wild activity.
- Analyzer(s) : Dohyun Lee (@l33d0hyun) of USELab, Korea University
Technical Details:
Overview:
FastRPC
is a framework for remote procedure calls betweenHLOS
andDSP
. In this framework, DSP manages memory mappings for DMA operations and updatesfd
values of DMA handles in theheader buffer
.
Analysis:
-
Reference Counting Issue
-
The vulnerability centers around improper management of three different types of reference counts:
refs
: Basic reference countctx_refs
: References in RPC contextdma_handle_refs
: References for DMA operations (added in patch)
Code Flow:
- Memory Map Structure:
struct fastrpc_mmap { struct hlist_node hn; struct fastrpc_file *fl; struct fastrpc_apps *apps; int fd; uint32_t flags; struct dma_buf *buf; uint64_t phys; size_t size; uintptr_t va; size_t len; int refs; // General reference count unsigned int ctx_refs; // RPC context references bool is_filemap; bool is_persistent; // ... other fields };
- Memory Map Handling:
static int fastrpc_mmap_remove(struct fastrpc_file *fl, int fd, uintptr_t va, size_t len, struct fastrpc_mmap **ppmap) { // Check global maps spin_lock_irqsave(&me->hlock, irq_flags); hlist_for_each_entry_safe(map, n, &me->maps, hn) { if ((fd < 0 || map->fd == fd) && map->raddr == va && map->raddr + map->len == va + len && map->refs == 1 && !map->is_persistent && !map->is_filemap) { match = map; hlist_del_init(&map->hn); break; } } spin_unlock_irqrestore(&me->hlock, irq_flags); // Check file-specific maps hlist_for_each_entry_safe(map, n, &fl->maps, hn) { if ((fd < 0 || map->fd == fd) && map->raddr == va && map->raddr + map->len == va + len && map->refs == 1 && !map->ctx_refs && !map->is_filemap) { match = map; hlist_del_init(&map->hn); break; } } }
- DMA Handle Processing:
static int put_args(uint32_t kernel, struct smq_invoke_ctx *ctx, remote_arg_t *upra) { // ... other code ... // Get fdlist from pages fdlist = (uint64_t *)&pages[bufs + handles]; mutex_lock(&ctx->fl->map_mutex); for (i = 0; i < M_FDLIST; i++) { if (!fdlist[i]) break; if (!fastrpc_mmap_find(ctx->fl, (int)fdlist[i], NULL, 0, 0, 0, 0, &mmap)) { if (mmap && mmap->ctx_refs) mmap->ctx_refs--; fastrpc_mmap_free(mmap, 0); } } mutex_unlock(&ctx->fl->map_mutex); }
Core Issues
- Header Buffer Manipulation:
- fdlist is derived from pages in header buffer
- Header buffer is accessible in unsigned protected domain
- No validation of fdlist contents from DSP
- Unsafe Memory Map Release:
if (!fastrpc_mmap_find(ctx->fl, (int)fdlist[i], NULL, 0, 0, 0, 0, &mmap)) { if (mmap && mmap->ctx_refs) mmap->ctx_refs--; fastrpc_mmap_free(mmap, 0); }
- Directly uses fd values from user-accessible buffer
- No verification if mapping is still in use by DSP
- Decrements refs and frees mapping without DMA state check
- Reference Counting Issues:
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_irqsave(&me->hlock, irq_flags); map->refs--; if (!map->refs && !map->is_persistent && !map->ctx_refs) hlist_del_init(&map->hn); // ... } else { map->refs--; if (!map->refs && !map->ctx_refs) hlist_del_init(&map->hn); if (map->refs > 0 && !flags) return; } }
- Only checks refs and
ctx_refs
- No tracking of
DMA operations
- Can free memory while DSP is still using it
- Only checks refs and
Exploitation Flow:
- Get valid fd of active mapping:
- Create legitimate FastRPC call
- Obtain fd of memory mapping
- Manipulate header buffer:
- Locate fdlist in header buffer
- Inject valid fd value
- Trigger put_args:
- System processes fdlist
- Finds legitimate mapping
- Prematurely frees mapping
- UAF condition:
- DSP continues to use freed mapping
- Memory corruption occurs
- Required Conditions:
- Access to FastRPC device
- Ability to manipulate header buffer
- Timing between DSP operations
Patch:
diff --git a/dsp/adsprpc.c b/dsp/adsprpc.c
index 6ec85443eb44153ae3e89c2d4d8d09af8672eb93..8cfb8e6f28b8629bc4d7171483a06a02a854cc4f 100644
--- a/dsp/adsprpc.c
+++ b/dsp/adsprpc.c
@@ -955,6 +955,8 @@ static int fastrpc_mmap_remove(struct fastrpc_file *fl, int fd, uintptr_t va,
map->refs == 1 &&
/* Remove map only if it isn't being used in any pending RPC calls */
!map->ctx_refs &&
+ /* Remove map only if it isn't being used by DSP */
+ !map->dma_handle_refs &&
/* Skip unmap if it is fastrpc shell memory */
!map->is_filemap) {
match = map;
@@ -994,8 +996,9 @@ 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_irqsave(&me->hlock, irq_flags);
- map->refs--;
- if (!map->refs && !map->is_persistent && !map->ctx_refs)
+ if (map->refs)
+ map->refs--;
+ if (!map->refs && !map->is_persistent)
hlist_del_init(&map->hn);
if (map->refs > 0) {
ADSPRPC_WARN(
@@ -1008,10 +1011,14 @@ static void fastrpc_mmap_free(struct fastrpc_mmap *map, uint32_t flags)
map->in_use = false;
spin_unlock_irqrestore(&me->hlock, irq_flags);
} 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 (flags || (!map->refs && !map->ctx_refs && !map->dma_handle_refs))
hlist_del_init(&map->hn);
- if (map->refs > 0 && !flags)
+ else
return;
}
if (map->flags == ADSP_MMAP_HEAP_ADDR ||
@@ -2501,21 +2508,22 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx)
if (err) {
for (j = bufs; j < i; j++) {
/*
- * Due to error decrement ctx refs count before mmap free
+ * Due to error decrement refs count before mmap free
* for each in/out handle, if map created
*/
- 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;
} else if (ctx->maps[i]) {
/*
- * Increment ctx refs count for in/out handle if map created
+ * Increment refs count for in/out handle if map created
* and no error, indicate map under use in remote call
*/
- ctx->maps[i]->ctx_refs++;
+ ctx->maps[i]->dma_handle_refs++;
}
ipage += 1;
}
@@ -2667,14 +2675,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];
+ 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], NULL, 0, 0,
+ 0, 0, &mmap)) {
+ if (mmap) {
+ pages[i].addr = mmap->phys;
+ pages[i].size = mmap->size;
+ }
- if (map) {
- pages[i].addr = map->phys;
- pages[i].size = map->size;
+ } else {
+ /* map already freed by some other call */
+ mutex_unlock(&ctx->fl->map_mutex);
+ ADSPRPC_ERR("could not find map associated with dma hadle 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 */
@@ -2883,9 +2910,10 @@ static int put_args(uint32_t kernel, struct smq_invoke_ctx *ctx,
* Decrement ctx refs count before mmap free,
* indicate remote call no longer using it
*/
- 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);
+ }
}
}
mutex_unlock(&ctx->fl->map_mutex);
diff --git a/dsp/adsprpc_shared.h b/dsp/adsprpc_shared.h
index cd8ed472ff39a33fed610f17b3265d11f7167b97..0b773fd74e1cc51150e5d3b7afeb6986f16da3d1 100644
--- a/dsp/adsprpc_shared.h
+++ b/dsp/adsprpc_shared.h
@@ -794,6 +794,8 @@ struct fastrpc_mmap {
char *servloc_name; /* Indicate which daemon mapped this */
/* Indicates map is being used by a pending RPC call */
unsigned int ctx_refs;
+ /* Map in use for dma handle */
+ unsigned int dma_handle_refs;
};
enum fastrpc_perfkeys {
- The patch:
- Adds
dma_handle_refs
field to track DMA usage - Makes memory freeing conditional on DMA completion
- Ensures synchronization between HLOS and DSP
- Prevents premature mapping release
- Adds
Proof-of-Concept:
TBA