Skip to contentSkip to conversation

dmaengine: Fix teardown races, resource leaks, and error handling

PATCH New Lee Jones 2 participants 15 messages Nov 11

Subsystems (8)
lore
Share & tools
lore

Lee Jones

author
#0 lore raw

cover dmaengine: Fix teardown races, resource leaks, and error handling

These issues were reported by Sashiko.

Cc Akinobu Mita <[email protected]>
Cc Alex Smith <[email protected]>
Cc Andrey Volkov <[email protected]>
Cc Basavaraj Natikar <[email protected]>
Cc Colin Ian King <[email protected]>
Cc Dale Farnsworth <[email protected]>
Cc Frank Li <[email protected]>
Cc Gary R Hook <[email protected]>
Cc Grant Likely <[email protected]>
Cc Jacky Huang <[email protected]>
Cc Joachim Eastwood <[email protected]>
Cc Linus Walleij <[email protected]>
Cc Paul Cercueil <[email protected]>
Cc Peter Pearse <[email protected]>
Cc Philipp Stanner <[email protected]>
Cc Raju Rangoju <[email protected]>
Cc Robin Murphy <[email protected]>
Cc Sanjay R Mehta <[email protected]>
Cc Shan-Chun Hung <[email protected]>
Cc Sylvain Munaut <[email protected]>
Cc Vinod Koul <[email protected]>
Cc Xianwei Zhao <[email protected]>
Cc Zubair Lutfullah Kakakhel <[email protected]>

Lee Jones (10):
dmaengine: pl08x: Defer OF DMA controller registration until slave
setup
dmaengine: ae4dma: Unregister DMA engine in ae4_pci_remove()
dmaengine: ptdma: Unmap descriptor in pt_do_cleanup()
dmaengine: ptdma: Remove completed descriptor under vc.lock in
pt_cmd_callback_work()
dmaengine: amlogic: Destroy free_wq after devres DMA device
unregistration
dmaengine: arm-dma350: Query dma_cookie_status() under vc.lock in

    d350_tx_status()

dmaengine: bestcomm: Defer publishing bcom_eng until initialisation
completes
dmaengine: bestcomm: Check rh_create() return value in
bcom_sram_init()
dmaengine: jz4780: Fix descriptor cleanup on hwdesc setup failure
dmaengine: dmatest: Avoid unsigned underflow in min_odd()

 drivers/dma/amba-pl08x.c                | 23 +++++++++
 drivers/dma/amd/ae4dma/ae4dma-pci.c     |  1 +
 drivers/dma/amd/ptdma/ptdma-dmaengine.c |  4 +-
 drivers/dma/amlogic-dma.c               |  3 +-
 drivers/dma/arm-dma350.c                |  3 +-
 drivers/dma/bestcomm/bestcomm.c         | 69 +++++++++++++------------
 drivers/dma/bestcomm/sram.c             |  6 +++
 drivers/dma/dma-jz4780.c                |  5 +-
 drivers/dma/dmatest.c                   |  6 +++
 9 files changed, 80 insertions(+), 40 deletions(-)
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#1 lore raw

PATCH 1/10 dmaengine: pl08x: Defer OF DMA controller registration until slave setup

pl08x_of_probe() registers the controller with of_dma_controller_register() before pl08x_dma_init_virtual_channels() initialises the slave channels list and before dma_async_device_register() registers the slave DMA device, and pl08x_probe() does not unregister the OF DMA controller if subsequent initialisation fails.

Move of_dma_controller_register() after dma_async_device_register() for the slave device and unwind slave device registration if OF controller registration fails.

Fixes aa4734da6674 ("dmaengine: pl08x: support dt channel assignment")
---
 drivers/dma/amba-pl08x.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
drivers/dma/amba-pl08x.c +23 -0
index 543c2eb8cfa8..d8441e338502 100644
@@ -2678,6 +2678,12 @@ static int pl08x_of_probe(struct amba_device *adev,
 
 	pl08x->pd = pd;
 
+	return 0;
+}
+
+static int pl08x_of_register(struct amba_device *adev,
+			     struct pl08x_driver_data *pl08x)
+{
 	return of_dma_controller_register(adev->dev.of_node, pl08x_of_xlate,
 					  pl08x);
 }
@@ -2688,6 +2694,12 @@ static inline int pl08x_of_probe(struct amba_device *adev,
 {
 	return -EINVAL;
 }
+
+static inline int pl08x_of_register(struct amba_device *adev,
+				    struct pl08x_driver_data *pl08x)
+{
+	return 0;
+}
 #endif
 
 static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
@@ -2944,6 +2956,15 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
 			__func__, ret);
 			goto out_no_slave_reg;
 		}
+		if (np) {
+			ret = pl08x_of_register(adev, pl08x);
+			if (ret) {
+				dev_warn(&pl08x->adev->dev,
+					 "%s failed to register OF DMA controller - %d\n",
+					 __func__, ret);
+				goto out_no_of_dma;
+			}
+		}
 	}
 
 	amba_set_drvdata(adev, pl08x);
@@ -2954,6 +2975,8 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
 
 	return 0;
 
+out_no_of_dma:
+	dma_async_device_unregister(&pl08x->slave);
 out_no_slave_reg:
 	dma_async_device_unregister(&pl08x->memcpy);
 out_no_memcpy_reg:
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#2 lore raw

PATCH 2/10 dmaengine: ae4dma: Unregister DMA engine in ae4_pci_remove()

In reply to Lee Jones

ae4_core_init() registers the DMA device and creates the descriptor slab cache via pt_dmaengine_register(), but ae4_pci_remove() never calls pt_dmaengine_unregister(), leaking the descriptor cache and leaving a dangling DMA device registered after device removal.

Export pt_dmaengine_unregister() and call it from ae4_pci_remove().

Fixes 98f5a4432622 ("dmaengine: ae4dma: Register AE4DMA using pt_dmaengine_register")
---
 drivers/dma/amd/ae4dma/ae4dma-pci.c     | 1 +
 drivers/dma/amd/ptdma/ptdma-dmaengine.c | 1 +
 2 files changed, 2 insertions(+)
drivers/dma/amd/ae4dma/ae4dma-pci.c +1 -0
index 2c63907db228..8712f4f65e28 100644
@@ -132,6 +132,7 @@ static void ae4_pci_remove(struct pci_dev *pdev)
 {
 	struct ae4_device *ae4 = dev_get_drvdata(&pdev->dev);
 
+	pt_dmaengine_unregister(&ae4->pt);
 	ae4_destroy_work(ae4);
 	ae4_deinit(ae4);
 }
drivers/dma/amd/ptdma/ptdma-dmaengine.c +1 -0
index 628c49ce5de9..22e7b3ae26a1 100644
@@ -657,3 +657,4 @@ void pt_dmaengine_unregister(struct pt_device *pt)
 
 	kmem_cache_destroy(pt->dma_desc_cache);
 }
+EXPORT_SYMBOL_GPL(pt_dmaengine_unregister);
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
#3 lore raw

Re: [PATCH 02/10] dmaengine: ae4dma: Unregister DMA engine in ae4_pci_remove()

Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Medium] dmaengine: ae4dma: slab use-after-free in ae4_pci_remove() teardown order --

Thank you for your contribution! Sashiko AI review found 1 potential issue to consider:
- [Medium] dmaengine: ae4dma: slab use-after-free in ae4_pci_remove() teardown order
--

commit d190c73fccb945a036c5abd1d18c37ce271e68d8
Author: Lee Jones <[email protected]>

dmaengine: ae4dma: Unregister DMA engine in ae4_pci_remove()

This commit exports pt_dmaengine_unregister() and calls it in ae4_pci_remove() to unregister the DMA device and destroy its descriptor slab cache on removal.

drivers/dma/amd/ae4dma/ae4dma-pci.c static void ae4_pci_remove(struct pci_dev *pdev) · line 138

Quoted 12 lines
drivers/dma/amd/ae4dma/ae4dma-pci.c +1 -0
index 8ca34d4457cc0..7f48ebc5b10c6 100644
@@ -132,6 +132,7 @@ static void ae4_pci_remove(struct pci_dev *pdev)
 {
 	struct ae4_device *ae4 = dev_get_drvdata(&pdev->dev);
 
+	pt_dmaengine_unregister(&ae4->pt);
 	ae4_destroy_work(ae4);
 	ae4_deinit(ae4);
 }

[Severity: Medium]
Can calling pt_dmaengine_unregister() before ae4_destroy_work() cause a use-after-free on pt->dma_desc_cache?

pt_dmaengine_unregister() calls dma_async_device_unregister() and then immediately destroys pt->dma_desc_cache, while the per-queue delayed work items on ae4cmd_q->pws are only stopped afterwards by ae4_destroy_work(). dma_async_device_unregister() does not drain ae4cmd_q->pws, and pt_synchronize() only synchronizes the vchan tasklet. If ae4_pending_work() is still running when the device is unbound, it can free a completed descriptor into pt->dma_desc_cache after the cache is destroyed:

CPU0 kworker
ae4_pci_remove() ae4_pending_work()
pt_dmaengine_unregister() cmd->pt_cmd_callback()

    dma_async_device_unregister()         pt_cmd_callback_work()
    kmem_cache_destroy(                     ...
      pt->dma_desc_cache)                   vchan_vdesc_fini()
                                              pt_do_cleanup()
                                                kmem_cache_free(
                                                  pt->dma_desc_cache, desc)
                                                  <-- use-after-free

ae4_destroy_work()

Stopping the workqueues before pt_dmaengine_unregister(), or destroying pt->dma_desc_cache after ae4_destroy_work(), would avoid this race.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author
#4 lore raw

PATCH 3/10 dmaengine: ptdma: Unmap descriptor in pt_do_cleanup()

In reply to Lee Jones

When descriptors are freed on abort via vchan_dma_desc_free_list(), pt_do_cleanup() frees the descriptor without calling dma_descriptor_unmap(), leaking any attached dmaengine_unmap_data and DMA mappings.

Call dma_descriptor_unmap(&vd->tx) in pt_do_cleanup() before freeing the descriptor.

Fixes b0b4a6b10577 ("dmaengine: ptdma: register PTDMA controller as a DMA resource")
---
 drivers/dma/amd/ptdma/ptdma-dmaengine.c | 1 +
 1 file changed, 1 insertion(+)
drivers/dma/amd/ptdma/ptdma-dmaengine.c +1 -0
index 22e7b3ae26a1..405d3d3b7f8a 100644
@@ -91,6 +91,7 @@ static void pt_do_cleanup(struct virt_dma_desc *vd)
 	struct pt_dma_desc *desc = to_pt_desc(vd);
 	struct pt_device *pt = desc->pt;
 
+	dma_descriptor_unmap(&vd->tx);
 	kmem_cache_free(pt->dma_desc_cache, desc);
 }
 
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#5 lore raw

PATCH 4/10 dmaengine: ptdma: Remove completed descriptor under vc.lock in pt_cmd_callback_work()

In reply to Lee Jones

pt_cmd_callback_work() drops chan->vc.lock before calling list_del(&desc->vd.node), racing with concurrent virt-dma list operations such as pt_terminate_all().

Move list_del(&desc->vd.node) inside the chan->vc.lock critical section, matching pt_handle_active_desc().

Fixes 656543989457 ("dmaengine: ptdma: Utilize the AE4DMA engine's multi-queue functionality")
---
 drivers/dma/amd/ptdma/ptdma-dmaengine.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/amd/ptdma/ptdma-dmaengine.c +1 -1
index 405d3d3b7f8a..da50c7d15348 100644
@@ -365,6 +365,7 @@ static void pt_cmd_callback_work(void *data, int err)
 
 		dma_cookie_complete(tx_desc);
 		dma_descriptor_unmap(tx_desc);
+		list_del(&desc->vd.node);
 	} else {
 		tx_desc = NULL;
 	}
@@ -373,7 +374,6 @@ static void pt_cmd_callback_work(void *data, int err)
 	if (tx_desc) {
 		dmaengine_desc_get_callback_invoke(tx_desc, NULL);
 		dma_run_dependencies(tx_desc);
-		list_del(&desc->vd.node);
 		vchan_vdesc_fini(vd);
 	}
 }
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#6 lore raw

PATCH 5/10 dmaengine: amlogic: Destroy free_wq after devres DMA device unregistration

In reply to Lee Jones

aml_dma_probe() registers the DMA device with dmaenginem_async_device_register(), so dma_async_device_unregister() is deferred until devres release after aml_dma_remove() returns. However, aml_dma_remove() destroys aml_dma->free_wq directly, causing aml_dma_free_desc() to queue work onto a destroyed workqueue when channels and descriptors are released during devres teardown, and early error returns in aml_dma_probe() also leak aml_dma->free_wq.

Allocate aml_dma->free_wq with devm_alloc_ordered_workqueue() so it is destroyed on probe failure and after dmaenginem_async_device_register() unwinds on removal.

Fixes b84b5197e344 ("dmaengine: amlogic: Add general DMA driver for A9")
---
 drivers/dma/amlogic-dma.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/dma/amlogic-dma.c +1 -2
index 684121d13fca..791bd82743d3 100644
@@ -620,7 +620,7 @@ static int aml_dma_probe(struct platform_device *pdev)
 	if (!aml_dma)
 		return -ENOMEM;
 
-	aml_dma->free_wq = alloc_ordered_workqueue("aml_dma_free", 0);
+	aml_dma->free_wq = devm_alloc_ordered_workqueue(&pdev->dev, "aml_dma_free", 0);
 	if (!aml_dma->free_wq)
 		return -ENOMEM;
 
@@ -712,7 +712,6 @@ static void aml_dma_remove(struct platform_device *pdev)
 		if (aml_chan)
 			tasklet_kill(&aml_chan->vchan.task);
 	}
-	destroy_workqueue(aml_dma->free_wq);
 }
 
 static const struct of_device_id aml_dma_ids[] = {
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
#7 lore raw

Re: [PATCH 05/10] dmaengine: amlogic: Destroy free_wq after devres DMA device unregistration

Thank you for your contribution! Sashiko AI review found no new issues in this patch, but noticed 1 related pre-existing issue it does not introduce: - [Medium] dmaengine: amlogic: missing…

Thank you for your contribution! Sashiko AI review found no new issues in this patch, but noticed 1 related pre-existing issue it does not introduce:
- [Medium] dmaengine: amlogic: missing platform_get_irq() error check in aml_dma_probe() [1]
--

commit 2bde83d0cee966b150690899ee8af15d0fb91330
Author: Lee Jones <[email protected]>

dmaengine: amlogic: Destroy free_wq after devres DMA device unregistration

This commit switches aml_dma->free_wq to devm_alloc_ordered_workqueue() so that the workqueue is cleaned up on probe failure and destroyed after devres unregisters the DMA device on removal.

drivers/dma/amlogic-dma.c static int aml_dma_probe(struct platform_device *pdev) · line 626

Quoted 13 lines
drivers/dma/amlogic-dma.c +1 -1
index 684121d13fcab..791bd82743d31 100644
@@ -620,7 +620,7 @@ static int aml_dma_probe(struct platform_device *pdev)
 	if (!aml_dma)
 		return -ENOMEM;
 
-	aml_dma->free_wq = alloc_ordered_workqueue("aml_dma_free", 0);
+	aml_dma->free_wq = devm_alloc_ordered_workqueue(&pdev->dev, "aml_dma_free", 0);
 	if (!aml_dma->free_wq)
 		return -ENOMEM;
 

[Severity: Medium]
This problem wasn't introduced by this patch, but should aml_dma_probe() check aml_dma->irq for errors after calling platform_get_irq() [1]?

drivers/dma/amlogic-dma.c:aml_dma_probe() {
    ...
	aml_dma->irq = platform_get_irq(pdev, 0);
    ...
	ret = devm_request_irq(&pdev->dev, aml_dma->irq, aml_dma_interrupt_handler,
			       0, dev_name(&pdev->dev), aml_dma);
}

If platform_get_irq() returns a negative error code such as -EPROBE_DEFER, passing aml_dma->irq to devm_request_irq() converts it to a large unsigned IRQ number that fails with -EINVAL, turning probe deferral into a permanent probe failure.

[1] https://sashiko.dev/#/bug/linux-bec1576a-66f2-4328-8c01-0836c0f63d2a

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
author
#8 lore raw

PATCH 6/10 dmaengine: arm-dma350: Query dma_cookie_status() under vc.lock in d350_tx_status()

In reply to Lee Jones

Calling dma_cookie_status() before acquiring dch->vc.lock in d350_tx_status() races with d350_irq() completing the active descriptor and starting the next queued transfer, causing d350_tx_status() to observe a stale DMA_IN_PROGRESS status and falsely return DMA_ERROR.

Move the dma_cookie_status() call inside the dch->vc.lock critical section.

Fixes 5d099706449d ("dmaengine: Add Arm DMA-350 driver")
---
 drivers/dma/arm-dma350.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/dma/arm-dma350.c +1 -2
index 52f5242ea332..8c1725588f1d 100644
@@ -381,9 +381,8 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
 	unsigned long flags;
 	u32 residue = 0;
 
+	spin_lock_irqsave(&dch->vc.lock, flags);
 	status = dma_cookie_status(chan, cookie, state);
-
-	spin_lock_irqsave(&dch->vc.lock, flags);
 	if (cookie == dch->cookie) {
 		status = dch->status;
 		if (status == DMA_IN_PROGRESS || status == DMA_PAUSED)
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#9 lore raw

PATCH 7/10 dmaengine: bestcomm: Defer publishing bcom_eng until initialisation completes

In reply to Lee Jones

mpc52xx_bcom_probe() assigns the newly allocated struct bcom_engine to the global bcom_eng pointer before calling bcom_engine_init(). A concurrent call to bcom_task_alloc() can observe a non-NULL bcom_eng pointer before its spinlock and task descriptor table are initialised.

Initialise the engine via a local pointer and only publish bcom_eng once initialisation has succeeded.

Fixes 2f9ea1bde0d1 ("[POWERPC] bestcomm: core bestcomm support for Freescale MPC5200")
---
 drivers/dma/bestcomm/bestcomm.c | 69 +++++++++++++++++----------------
 1 file changed, 36 insertions(+), 33 deletions(-)
drivers/dma/bestcomm/bestcomm.c +36 -33
index 241a91936fe8..dbc3ce5fc446 100644
@@ -270,7 +270,7 @@ static u32 fdt_ops[] = {
 };
 
 
-static int bcom_engine_init(void)
+static int bcom_engine_init(struct bcom_engine *eng)
 {
 	int task;
 	phys_addr_t tdt_pa, ctx_pa, var_pa, fdt_pa;
@@ -282,56 +282,59 @@ static int bcom_engine_init(void)
 	var_size = BCOM_MAX_TASKS * (BCOM_VAR_SIZE + BCOM_INC_SIZE);
 	fdt_size = BCOM_FDT_SIZE;
 
-	bcom_eng->tdt = bcom_sram_alloc(tdt_size, sizeof(u32), &tdt_pa);
-	bcom_eng->ctx = bcom_sram_alloc(ctx_size, BCOM_CTX_ALIGN, &ctx_pa);
-	bcom_eng->var = bcom_sram_alloc(var_size, BCOM_VAR_ALIGN, &var_pa);
-	bcom_eng->fdt = bcom_sram_alloc(fdt_size, BCOM_FDT_ALIGN, &fdt_pa);
+	eng->tdt = bcom_sram_alloc(tdt_size, sizeof(u32), &tdt_pa);
+	eng->ctx = bcom_sram_alloc(ctx_size, BCOM_CTX_ALIGN, &ctx_pa);
+	eng->var = bcom_sram_alloc(var_size, BCOM_VAR_ALIGN, &var_pa);
+	eng->fdt = bcom_sram_alloc(fdt_size, BCOM_FDT_ALIGN, &fdt_pa);
 
-	if (!bcom_eng->tdt || !bcom_eng->ctx || !bcom_eng->var || !bcom_eng->fdt) {
+	if (!eng->tdt || !eng->ctx || !eng->var || !eng->fdt) {
 		printk(KERN_ERR "DMA: SRAM alloc failed in engine init !\n");
 
-		bcom_sram_free(bcom_eng->tdt);
-		bcom_sram_free(bcom_eng->ctx);
-		bcom_sram_free(bcom_eng->var);
-		bcom_sram_free(bcom_eng->fdt);
+		bcom_sram_free(eng->tdt);
+		bcom_sram_free(eng->ctx);
+		bcom_sram_free(eng->var);
+		bcom_sram_free(eng->fdt);
 
 		return -ENOMEM;
 	}
 
-	memset_io(bcom_eng->tdt, 0x00, tdt_size);
-	memset_io(bcom_eng->ctx, 0x00, ctx_size);
-	memset_io(bcom_eng->var, 0x00, var_size);
-	memset_io(bcom_eng->fdt, 0x00, fdt_size);
+	memset_io(eng->tdt, 0x00, tdt_size);
+	memset_io(eng->ctx, 0x00, ctx_size);
+	memset_io(eng->var, 0x00, var_size);
+	memset_io(eng->fdt, 0x00, fdt_size);
 
 	/* Copy the FDT for the EU#3 */
-	memcpy_toio(&bcom_eng->fdt[48], fdt_ops, sizeof(fdt_ops));
+	memcpy_toio(&eng->fdt[48], fdt_ops, sizeof(fdt_ops));
 
 	/* Initialize Task base structure */
 	for (task=0; task<BCOM_MAX_TASKS; task++)
 	{
-		out_be16(&bcom_eng->regs->tcr[task], 0);
-		out_8(&bcom_eng->regs->ipr[task], 0);
+		out_be16(&eng->regs->tcr[task], 0);
+		out_8(&eng->regs->ipr[task], 0);
 
-		bcom_eng->tdt[task].context	= ctx_pa;
-		bcom_eng->tdt[task].var	= var_pa;
-		bcom_eng->tdt[task].fdt	= fdt_pa;
+		eng->tdt[task].context	= ctx_pa;
+		eng->tdt[task].var	= var_pa;
+		eng->tdt[task].fdt	= fdt_pa;
 
 		var_pa += BCOM_VAR_SIZE + BCOM_INC_SIZE;
 		ctx_pa += BCOM_CTX_SIZE;
 	}
 
-	out_be32(&bcom_eng->regs->taskBar, tdt_pa);
+	out_be32(&eng->regs->taskBar, tdt_pa);
 
 	/* Init 'always' initiator */
-	out_8(&bcom_eng->regs->ipr[BCOM_INITIATOR_ALWAYS], BCOM_IPR_ALWAYS);
+	out_8(&eng->regs->ipr[BCOM_INITIATOR_ALWAYS], BCOM_IPR_ALWAYS);
+
+	/* Init lock */
+	spin_lock_init(&eng->lock);
+
+	/* Ensure all engine fields are visible before publishing bcom_eng */
+	smp_store_release(&bcom_eng, eng);
 
 	/* Disable COMM Bus Prefetch on the original 5200; it's broken */
 	if ((mfspr(SPRN_SVR) & MPC5200_SVR_MASK) == MPC5200_SVR)
 		bcom_disable_prefetch();
 
-	/* Init lock */
-	spin_lock_init(&bcom_eng->lock);
-
 	return 0;
 }
 
@@ -363,6 +366,7 @@ bcom_engine_cleanup(void)
 
 static int mpc52xx_bcom_probe(struct platform_device *op)
 {
+	struct bcom_engine *eng;
 	struct device_node *ofn_sram;
 	struct resource *res_bcom;
 	void __iomem *regs;
@@ -398,20 +402,20 @@ static int mpc52xx_bcom_probe(struct platform_device *op)
 	}
 
 	/* Get a clean struct */
-	bcom_eng = kzalloc_obj(struct bcom_engine);
-	if (!bcom_eng) {
+	eng = kzalloc_obj(struct bcom_engine);
+	if (!eng) {
 		rv = -ENOMEM;
 		goto error_sramclean;
 	}
 
 	/* Save the node */
-	bcom_eng->ofnode = op->dev.of_node;
+	eng->ofnode = op->dev.of_node;
 
-	bcom_eng->regs = regs;
-	bcom_eng->regs_base = res_bcom->start;
+	eng->regs = regs;
+	eng->regs_base = res_bcom->start;
 
 	/* Now, do the real init */
-	rv = bcom_engine_init();
+	rv = bcom_engine_init(eng);
 	if (rv)
 		goto error_sramclean;
 
@@ -423,8 +427,7 @@ static int mpc52xx_bcom_probe(struct platform_device *op)
 
 	/* Error path */
 error_sramclean:
-	kfree(bcom_eng);
-	bcom_eng = NULL;
+	kfree(eng);
 	bcom_sram_cleanup();
 error_ofput:
 	of_node_put(op->dev.of_node);
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
#10 lore raw

Re: [PATCH 07/10] dmaengine: bestcomm: Defer publishing bcom_eng until initialisation completes

Thank you for your contribution! Sashiko AI review found no new issues in this patch, but noticed 1 related pre-existing issue it does not introduce: - [Critical] dmaengine: bestcomm: use-after-free…

Thank you for your contribution! Sashiko AI review found no new issues in this patch, but noticed 1 related pre-existing issue it does not introduce:
- [Critical] dmaengine: bestcomm: use-after-free in bcom_task_alloc()
[1]
--

commit 0c7452c3ef4427c8251608e31eaba52df094162d
Author: Lee Jones <[email protected]>

dmaengine: bestcomm: Defer publishing bcom_eng until initialisation completes

This commit initializes struct bcom_engine via a local pointer and defers publishing it to the global bcom_eng pointer until initialization in bcom_engine_init() has completed.

Quoted 4 lines
drivers/dma/bestcomm/bestcomm.c +0 -0
index 241a91936fe8a..dbc3ce5fc4462 100644

[ ... ]

Quoted 28 lines
drivers/dma/bestcomm/bestcomm.c +6 -6
@@ -398,20 +402,20 @@ static int mpc52xx_bcom_probe(struct platform_device *op)
 	}
 
 	/* Get a clean struct */
-	bcom_eng = kzalloc_obj(struct bcom_engine);
-	if (!bcom_eng) {
+	eng = kzalloc_obj(struct bcom_engine);
+	if (!eng) {
 		rv = -ENOMEM;
 		goto error_sramclean;
 	}
 
 	/* Save the node */
-	bcom_eng->ofnode = op->dev.of_node;
+	eng->ofnode = op->dev.of_node;
 
-	bcom_eng->regs = regs;
-	bcom_eng->regs_base = res_bcom->start;
+	eng->regs = regs;
+	eng->regs_base = res_bcom->start;
 
 	/* Now, do the real init */
-	rv = bcom_engine_init();
+	rv = bcom_engine_init(eng);
 	if (rv)
 		goto error_sramclean;
 

drivers/dma/bestcomm/bestcomm.c static int mpc52xx_bcom_probe(struct platform_device *op) · line 433

Quoted 9 lines
drivers/dma/bestcomm/bestcomm.c +1 -2
@@ -423,8 +427,7 @@ static int mpc52xx_bcom_probe(struct platform_device *op)
 
 	/* Error path */
 error_sramclean:
-	kfree(bcom_eng);
-	bcom_eng = NULL;
+	kfree(eng);
 	bcom_sram_cleanup();
 error_ofput:
 	of_node_put(op->dev.of_node);

[Severity: Critical]
This problem wasn't introduced by this patch, but can mpc52xx_bcom_remove() trigger a use-after-free in bcom_task_alloc() [1]?

While this patch fixes premature publication of bcom_eng during probe, mpc52xx_bcom_remove() frees bcom_eng before clearing the global pointer and without synchronizing against concurrent bcom_task_alloc() callers:

CPU0 CPU1
bcom_task_alloc()
if (!bcom_eng)

                                  mpc52xx_bcom_remove()
                                    kfree(bcom_eng);
                                    bcom_eng = NULL;

spin_lock(&bcom_eng->lock); <-- use-after-free

[1] https://sashiko.dev/#/bug/linux-149609ff-866f-4c27-8adf-ef935bca5f89

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
author
#11 lore raw

PATCH 8/10 dmaengine: bestcomm: Check rh_create() return value in bcom_sram_init()

In reply to Lee Jones

rh_create() returns ERR_PTR(-ENOMEM) on allocation failure. bcom_sram_init() assigns this return value directly to bcom_sram->rh without checking IS_ERR() and passes it to rh_attach_region(), which dereferences the error pointer.

Check IS_ERR(bcom_sram->rh) after rh_create() and unmap the SRAM region on failure.

Fixes 2f9ea1bde0d1 ("[POWERPC] bestcomm: core bestcomm support for Freescale MPC5200")
---
 drivers/dma/bestcomm/sram.c | 6 ++++++
 1 file changed, 6 insertions(+)
drivers/dma/bestcomm/sram.c +6 -0
index 09ef5142c26c..df4d90cee465 100644
@@ -88,6 +88,10 @@ int bcom_sram_init(struct device_node *sram_node, char *owner)
 
 	/* Create an rheap (defaults to 32 bits word alignment) */
 	bcom_sram->rh = rh_create(4);
+	if (IS_ERR(bcom_sram->rh)) {
+		rv = PTR_ERR(bcom_sram->rh);
+		goto error_unmap;
+	}
 
 	/* Attach the free zones */
 	regaddr_p = NULL;
@@ -111,6 +115,8 @@ int bcom_sram_init(struct device_node *sram_node, char *owner)
 
 	return 0;
 
+error_unmap:
+	iounmap((void __iomem *)bcom_sram->base_virt);
 error_release:
 	release_mem_region(res.start, resource_size(&res));
 error_free:
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
author
#12 lore raw

PATCH 9/10 dmaengine: jz4780: Fix descriptor cleanup on hwdesc setup failure

In reply to Lee Jones

When jz4780_dma_setup_hwdesc() fails in jz4780_dma_prep_slave_sg() or jz4780_dma_prep_dma_cyclic(), the error paths pass &jzchan->desc->vdesc to jz4780_dma_desc_free() instead of the newly allocated &desc->vdesc. This leaks the newly allocated descriptor and either dereferences NULL when jzchan->desc is NULL or frees the currently active descriptor while it is still in flight. Furthermore, jz4780_dma_desc_free() derives jzchan from vdesc->tx.chan, which is not yet initialised before vchan_tx_prep() is called.

Initialise desc->vdesc.tx.chan in jz4780_dma_desc_alloc() and pass &desc->vdesc to jz4780_dma_desc_free() in the preparation error paths.

Fixes d894fc6046fe ("dmaengine: jz4780: add driver for the Ingenic JZ4780 DMA controller")
---
 drivers/dma/dma-jz4780.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/dma/dma-jz4780.c +3 -2
index ac2094e65ecf..c3974c825ba7 100644
@@ -248,6 +248,7 @@ jz4780_dma_desc_alloc(struct jz4780_dma_chan *jzchan, unsigned int count,
 		return NULL;
 	}
 
+	desc->vdesc.tx.chan = &jzchan->vchan.chan;
 	desc->count = count;
 	desc->type = type;
 
@@ -379,7 +380,7 @@ static struct dma_async_tx_descriptor *jz4780_dma_prep_slave_sg(
 					      sg_dma_len(&sgl[i]),
 					      direction);
 		if (err < 0) {
-			jz4780_dma_desc_free(&jzchan->desc->vdesc);
+			jz4780_dma_desc_free(&desc->vdesc);
 			return NULL;
 		}
 
@@ -426,7 +427,7 @@ static struct dma_async_tx_descriptor *jz4780_dma_prep_dma_cyclic(
 		err = jz4780_dma_setup_hwdesc(jzchan, &desc->desc[i], buf_addr,
 					      period_len, direction);
 		if (err < 0) {
-			jz4780_dma_desc_free(&jzchan->desc->vdesc);
+			jz4780_dma_desc_free(&desc->vdesc);
 			return NULL;
 		}
 
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog
#13 lore raw

Re: [PATCH 09/10] dmaengine: jz4780: Fix descriptor cleanup on hwdesc setup failure

Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Low] dmaengine: jz4780: Fixes trailer cites wrong introducing commit --

Thank you for your contribution! Sashiko AI review found 1 potential issue to consider:
- [Low] dmaengine: jz4780: Fixes trailer cites wrong introducing commit --

commit 689fd02184422d0b30aad68612dbfd11c23b8306
Author: Lee Jones <[email protected]>

Quoted 11 lines

dmaengine: jz4780: Fix descriptor cleanup on hwdesc setup failure

When jz4780_dma_setup_hwdesc() fails in jz4780_dma_prep_slave_sg() or jz4780_dma_prep_dma_cyclic(), the error paths pass &jzchan->desc->vdesc to jz4780_dma_desc_free() instead of the newly allocated &desc->vdesc. This leaks the newly allocated descriptor and either dereferences NULL when jzchan->desc is NULL or frees the currently active descriptor while it is still in flight. Furthermore, jz4780_dma_desc_free() derives jzchan from vdesc->tx.chan, which is not yet initialised before vchan_tx_prep() is called.

Quoted 7 lines

Initialise desc->vdesc.tx.chan in jz4780_dma_desc_alloc() and pass &desc->vdesc to jz4780_dma_desc_free() in the preparation error paths.

Fixes d894fc6046fe ("dmaengine: jz4780: add driver for the Ingenic JZ4780 DMA controller")

[Severity: Low]
In d894fc6046fe ("dmaengine: jz4780: add driver for the Ingenic JZ4780 DMA controller"), the preparation error paths did not call jz4780_dma_desc_free() at all. The broken jz4780_dma_desc_free(&jzchan->desc->vdesc) calls before vchan_tx_prep() were added later by fc878efe84c3 ("dmaengine: jz4780: fix resource leaks on error exit return"), so the Fixes tag should be:

Fixes fc878efe84c3 ("dmaengine: jz4780: fix resource leaks on error exit return")
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
author
#14 lore raw

PATCH 10/10 dmaengine: dmatest: Avoid unsigned underflow in min_odd()

In reply to Lee Jones

When either argument to min_odd() is zero (for example when a DMA device reports max_xor or max_pq as 0), val evaluates to 0 and val - 1 underflows to UINT_MAX. In dmatest_func(), this sets src->cnt to UINT_MAX, which wraps around the (src->cnt + dst->cnt) >= 255 check and causes dmatest_alloc_test_data() to pass 0 to kcalloc() and dereference ZERO_SIZE_PTR.

Return 0 from min_odd() when the minimum value is 0, and abort dmatest_func() if src->cnt is 0.

Fixes 8be9e32b310c ("dmatest: adjust invalid module parameters for number of source buffers")
---
 drivers/dma/dmatest.c | 6 ++++++
 1 file changed, 6 insertions(+)
drivers/dma/dmatest.c +6 -0
index 2ae3469397f3..5f768bb9a29f 100644
@@ -457,6 +457,9 @@ static unsigned int min_odd(unsigned int x, unsigned int y)
 {
 	unsigned int val = min(x, y);
 
+	if (!val)
+		return 0;
+
 	return val % 2 ? val : val - 1;
 }
 
@@ -650,6 +653,9 @@ static int dmatest_func(void *data)
 	} else
 		goto err_thread_type;
 
+	if (!src->cnt)
+		goto err_free_coefs;
+
 	/* Check if buffer count fits into map count variable (u8) */
 	if ((src->cnt + dst->cnt) >= 255) {
 		pr_err("too many buffers (%d of 255 supported)\n",
Signature · 1 line
2.56.0.567.gdb80ae8cd5-goog