Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

dmaengine: mmp_pdma: Fix race condition in mmp_pdma_residue()

Add proper locking in mmp_pdma_residue() to prevent use-after-free when
accessing descriptor list and descriptor contents.

The race occurs when multiple threads call tx_status() while the tasklet
on another CPU is freeing completed descriptors:

CPU 0 CPU 1
----- -----
mmp_pdma_tx_status()
mmp_pdma_residue()
-> NO LOCK held
list_for_each_entry(sw, ..)
DMA interrupt
dma_do_tasklet()
-> spin_lock(&desc_lock)
list_move(sw->node, ...)
spin_unlock(&desc_lock)
| dma_pool_free(sw) <- FREED!
-> access sw->desc <- UAF!

This issue can be reproduced when running dmatest on the same channel with
multiple threads (threads_per_chan > 1).

Fix by protecting the chain_running list iteration and descriptor access
with the chan->desc_lock spinlock.

Signed-off-by: Juan Li <lijuan@linux.spacemit.com>
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Link: https://patch.msgid.link/20251216-mmp-pdma-race-v1-1-976a224bb622@riscstar.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>

authored by

Guodong Xu and committed by
Vinod Koul
a1435458 430f7803

+6
+6
drivers/dma/mmp_pdma.c
··· 928 928 { 929 929 struct mmp_pdma_desc_sw *sw; 930 930 struct mmp_pdma_device *pdev = to_mmp_pdma_dev(chan->chan.device); 931 + unsigned long flags; 931 932 u64 curr; 932 933 u32 residue = 0; 933 934 bool passed = false; ··· 945 944 curr = pdev->ops->read_dst_addr(chan->phy); 946 945 else 947 946 curr = pdev->ops->read_src_addr(chan->phy); 947 + 948 + spin_lock_irqsave(&chan->desc_lock, flags); 948 949 949 950 list_for_each_entry(sw, &chan->chain_running, node) { 950 951 u64 start, end; ··· 992 989 continue; 993 990 994 991 if (sw->async_tx.cookie == cookie) { 992 + spin_unlock_irqrestore(&chan->desc_lock, flags); 995 993 return residue; 996 994 } else { 997 995 residue = 0; 998 996 passed = false; 999 997 } 1000 998 } 999 + 1000 + spin_unlock_irqrestore(&chan->desc_lock, flags); 1001 1001 1002 1002 /* We should only get here in case of cyclic transactions */ 1003 1003 return residue;