[PATCH 2/2] dmaengine: sf-pdma: Fix possible double-free for descriptors

Nikita Proshkin n.proshkin at yadro.com
Mon Nov 11 07:26:00 PST 2024


sf_pdma_issue_pending() sets pointer to the currently being processed
dma descriptor in struct sf_pdma_chan, but this descriptor remains a part
of the desc_issued list from the struct virt_dma_chan.

Descriptor is correctly deleted from the list and freed in done tasklet,
but stays in place in case of an error during dma processing, waiting for
sf_pdma_terminate_all() to be called.

If the pointer to the descriptor is valid in struct sf_pdma_chan,
sf_pdma_terminate_all() first frees this descriptor directly, but later
uses it again in the vchan_dma_desc_free_list(), leading to mem management
errors (double-free, use-after-free).

Signed-off-by: Nikita Proshkin <n.proshkin at yadro.com>
---
 drivers/dma/sf-pdma/sf-pdma.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
index 55b7c57eeec9..8fec11ad4f0b 100644
--- a/drivers/dma/sf-pdma/sf-pdma.c
+++ b/drivers/dma/sf-pdma/sf-pdma.c
@@ -145,7 +145,6 @@ static void sf_pdma_free_chan_resources(struct dma_chan *dchan)
 
 	spin_lock_irqsave(&chan->vchan.lock, flags);
 	sf_pdma_disable_request(chan);
-	kfree(chan->desc);
 	chan->desc = NULL;
 	vchan_get_all_descriptors(&chan->vchan, &head);
 	sf_pdma_disclaim_chan(chan);
@@ -192,7 +191,6 @@ static int sf_pdma_terminate_all(struct dma_chan *dchan)
 
 	spin_lock_irqsave(&chan->vchan.lock, flags);
 	sf_pdma_disable_request(chan);
-	kfree(chan->desc);
 	chan->desc = NULL;
 	vchan_get_all_descriptors(&chan->vchan, &head);
 	spin_unlock_irqrestore(&chan->vchan.lock, flags);
@@ -279,8 +277,10 @@ static void sf_pdma_donebh_tasklet(struct tasklet_struct *t)
 	spin_lock_irqsave(&chan->vchan.lock, flags);
 	chan->status = DMA_COMPLETE;
 
-	list_del(&chan->desc->vdesc.node);
-	vchan_cookie_complete(&chan->desc->vdesc);
+	if (chan->desc) {
+		list_del(&chan->desc->vdesc.node);
+		vchan_cookie_complete(&chan->desc->vdesc);
+	}
 
 	chan->desc = sf_pdma_get_first_pending_desc(chan);
 	if (chan->desc)
@@ -295,7 +295,8 @@ static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)
 	unsigned long flags;
 
 	spin_lock_irqsave(&chan->vchan.lock, flags);
-	dmaengine_desc_get_callback_invoke(chan->desc->async_tx, NULL);
+	if (chan->desc)
+		dmaengine_desc_get_callback_invoke(chan->desc->async_tx, NULL);
 	chan->status = DMA_ERROR;
 	spin_unlock_irqrestore(&chan->vchan.lock, flags);
 }
@@ -310,7 +311,7 @@ static irqreturn_t sf_pdma_done_isr(int irq, void *dev_id)
 	writel((readl(regs->ctrl)) & ~PDMA_DONE_STATUS_MASK, regs->ctrl);
 	residue = readq(regs->residue);
 
-	if (!residue) {
+	if (!residue || !chan->desc) {
 		tasklet_hi_schedule(&chan->done_tasklet);
 	} else {
 		/* submit next trascatioin if possible */
-- 
2.34.1




More information about the linux-riscv mailing list