[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