All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-27 16:58 [PATCH v2 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá
@ 2026-03-27 16:58   ` Nuno Sá via B4 Relay
  0 siblings, 0 replies; 8+ messages in thread
From: Nuno Sá @ 2026-03-27 16:58 UTC (permalink / raw)
  To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li

The DMA device lifetime can extend beyond the platform driver unbind if
DMA channels are still referenced by client drivers. This leads to
use-after-free when the devm-managed memory is freed on unbind but the
DMA device callbacks still access it.

Fix this by:
 - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
its lifetime is not tied to the platform device.
 - Implementing the device_release callback that so that we can free
the object when reference count gets to 0 (no users).
 - Adding an 'unbound' flag protected by the vchan lock that is set
during driver removal, preventing MMIO accesses after the device has been
unbound.

While at it, explicitly include spinlock.h given it was missing.

Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
 drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 60 insertions(+), 10 deletions(-)

diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index 127c3cf80a0e..70d3ad7e7d37 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -24,6 +24,7 @@
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
+#include <linux/spinlock.h>
 
 #include <dt-bindings/dma/axi-dmac.h>
 
@@ -174,6 +175,8 @@ struct axi_dmac {
 
 	struct dma_device dma_dev;
 	struct axi_dmac_chan chan;
+
+	bool unbound;
 };
 
 static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
@@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
 		dma_dev);
 }
 
+static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
+{
+	return container_of(dev, struct axi_dmac, dma_dev);
+}
+
 static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
 {
 	return container_of(c, struct axi_dmac_chan, vchan.chan);
@@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
 	LIST_HEAD(head);
 
 	spin_lock_irqsave(&chan->vchan.lock, flags);
-	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
+	/*
+	 * Only allow the MMIO access if the device is live. Otherwise still
+	 * go for freeing the descriptors.
+	 */
+	if (!dmac->unbound)
+		axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
 	chan->next_desc = NULL;
 	vchan_get_all_descriptors(&chan->vchan, &head);
 	list_splice_tail_init(&chan->active_descs, &head);
@@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
 	if (chan->hw_sg)
 		ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
 
-	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
-
 	spin_lock_irqsave(&chan->vchan.lock, flags);
+	if (dmac->unbound) {
+		spin_unlock_irqrestore(&chan->vchan.lock, flags);
+		return;
+	}
+	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
 	if (vchan_issue_pending(&chan->vchan))
 		axi_dmac_start_transfer(chan);
 	spin_unlock_irqrestore(&chan->vchan.lock, flags);
@@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
 	return 0;
 }
 
+static void axi_dmac_release(struct dma_device *dma_dev)
+{
+	struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
+
+	put_device(dma_dev->dev);
+	kfree(dmac);
+}
+
 static void axi_dmac_tasklet_kill(void *task)
 {
 	tasklet_kill(task);
@@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
 	of_dma_controller_free(of_node);
 }
 
+static void axi_dmac_disable(void *__dmac)
+{
+	struct axi_dmac *dmac = __dmac;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
+	dmac->unbound = true;
+	spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
+	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
+}
+
 static int axi_dmac_probe(struct platform_device *pdev)
 {
 	struct dma_device *dma_dev;
-	struct axi_dmac *dmac;
+	struct axi_dmac *__dmac;
 	struct regmap *regmap;
 	unsigned int version;
 	u32 irq_mask = 0;
 	int ret;
 
-	dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
+	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
 	if (!dmac)
 		return -ENOMEM;
 
@@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	dma_dev->dev = &pdev->dev;
 	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
 	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
+	dma_dev->device_release = axi_dmac_release;
 	dma_dev->directions = BIT(dmac->chan.direction);
 	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
 	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
@@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	/*
+	 * From this point on, our dmac object has it's lifetime bounded with
+	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
+	 * no more automatic kfree(). Also note that dmac is now NULL so we
+	 * need __dmac.
+	 */
+	__dmac = no_free_ptr(dmac);
+	get_device(&pdev->dev);
+
 	/*
 	 * Put the action in here so it get's done before unregistering the DMA
 	 * device.
 	 */
 	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
-				       &dmac->chan.vchan.task);
+				       &__dmac->chan.vchan.task);
 	if (ret)
 		return ret;
 
@@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
-			       IRQF_SHARED, dev_name(&pdev->dev), dmac);
+	/* So that we can mark the device as unbound and disable it */
+	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
 	if (ret)
 		return ret;
 
-	regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
-		 &axi_dmac_regmap_config);
+	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
+			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
+	if (ret)
+		return ret;
+
+	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
+				       &axi_dmac_regmap_config);
 
 	return PTR_ERR_OR_ZERO(regmap);
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
@ 2026-03-27 16:58   ` Nuno Sá via B4 Relay
  0 siblings, 0 replies; 8+ messages in thread
From: Nuno Sá via B4 Relay @ 2026-03-27 16:58 UTC (permalink / raw)
  To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li

From: Nuno Sá <nuno.sa@analog.com>

The DMA device lifetime can extend beyond the platform driver unbind if
DMA channels are still referenced by client drivers. This leads to
use-after-free when the devm-managed memory is freed on unbind but the
DMA device callbacks still access it.

Fix this by:
 - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
its lifetime is not tied to the platform device.
 - Implementing the device_release callback that so that we can free
the object when reference count gets to 0 (no users).
 - Adding an 'unbound' flag protected by the vchan lock that is set
during driver removal, preventing MMIO accesses after the device has been
unbound.

While at it, explicitly include spinlock.h given it was missing.

Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
 drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 60 insertions(+), 10 deletions(-)

diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index 127c3cf80a0e..70d3ad7e7d37 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -24,6 +24,7 @@
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
+#include <linux/spinlock.h>
 
 #include <dt-bindings/dma/axi-dmac.h>
 
@@ -174,6 +175,8 @@ struct axi_dmac {
 
 	struct dma_device dma_dev;
 	struct axi_dmac_chan chan;
+
+	bool unbound;
 };
 
 static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
@@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
 		dma_dev);
 }
 
+static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
+{
+	return container_of(dev, struct axi_dmac, dma_dev);
+}
+
 static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
 {
 	return container_of(c, struct axi_dmac_chan, vchan.chan);
@@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
 	LIST_HEAD(head);
 
 	spin_lock_irqsave(&chan->vchan.lock, flags);
-	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
+	/*
+	 * Only allow the MMIO access if the device is live. Otherwise still
+	 * go for freeing the descriptors.
+	 */
+	if (!dmac->unbound)
+		axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
 	chan->next_desc = NULL;
 	vchan_get_all_descriptors(&chan->vchan, &head);
 	list_splice_tail_init(&chan->active_descs, &head);
@@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
 	if (chan->hw_sg)
 		ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
 
-	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
-
 	spin_lock_irqsave(&chan->vchan.lock, flags);
+	if (dmac->unbound) {
+		spin_unlock_irqrestore(&chan->vchan.lock, flags);
+		return;
+	}
+	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
 	if (vchan_issue_pending(&chan->vchan))
 		axi_dmac_start_transfer(chan);
 	spin_unlock_irqrestore(&chan->vchan.lock, flags);
@@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
 	return 0;
 }
 
+static void axi_dmac_release(struct dma_device *dma_dev)
+{
+	struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
+
+	put_device(dma_dev->dev);
+	kfree(dmac);
+}
+
 static void axi_dmac_tasklet_kill(void *task)
 {
 	tasklet_kill(task);
@@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
 	of_dma_controller_free(of_node);
 }
 
+static void axi_dmac_disable(void *__dmac)
+{
+	struct axi_dmac *dmac = __dmac;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
+	dmac->unbound = true;
+	spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
+	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
+}
+
 static int axi_dmac_probe(struct platform_device *pdev)
 {
 	struct dma_device *dma_dev;
-	struct axi_dmac *dmac;
+	struct axi_dmac *__dmac;
 	struct regmap *regmap;
 	unsigned int version;
 	u32 irq_mask = 0;
 	int ret;
 
-	dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
+	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
 	if (!dmac)
 		return -ENOMEM;
 
@@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	dma_dev->dev = &pdev->dev;
 	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
 	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
+	dma_dev->device_release = axi_dmac_release;
 	dma_dev->directions = BIT(dmac->chan.direction);
 	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
 	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
@@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	/*
+	 * From this point on, our dmac object has it's lifetime bounded with
+	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
+	 * no more automatic kfree(). Also note that dmac is now NULL so we
+	 * need __dmac.
+	 */
+	__dmac = no_free_ptr(dmac);
+	get_device(&pdev->dev);
+
 	/*
 	 * Put the action in here so it get's done before unregistering the DMA
 	 * device.
 	 */
 	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
-				       &dmac->chan.vchan.task);
+				       &__dmac->chan.vchan.task);
 	if (ret)
 		return ret;
 
@@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
-			       IRQF_SHARED, dev_name(&pdev->dev), dmac);
+	/* So that we can mark the device as unbound and disable it */
+	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
 	if (ret)
 		return ret;
 
-	regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
-		 &axi_dmac_regmap_config);
+	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
+			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
+	if (ret)
+		return ret;
+
+	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
+				       &axi_dmac_regmap_config);
 
 	return PTR_ERR_OR_ZERO(regmap);
 }

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
@ 2026-03-29 23:40 kernel test robot
  2026-03-30 14:32 ` Nuno Sá
  0 siblings, 1 reply; 8+ messages in thread
From: kernel test robot @ 2026-03-29 23:40 UTC (permalink / raw)
  To: oe-kbuild; +Cc: lkp, Dan Carpenter

BCC: lkp@intel.com
CC: oe-kbuild-all@lists.linux.dev
In-Reply-To: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
References: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
TO: "Nuno Sá via B4 Relay" <devnull+nuno.sa.analog.com@kernel.org>
TO: dmaengine@vger.kernel.org
TO: linux-kernel@vger.kernel.org
CC: "Lars-Peter Clausen" <lars@metafoo.de>
CC: Vinod Koul <vkoul@kernel.org>
CC: Frank Li <Frank.Li@kernel.org>

Hi Nuno,

kernel test robot noticed the following build warnings:

[auto build test WARNING on b7560798466a07d9c3fb011698e92c335ab28baf]

url:    https://github.com/intel-lab-lkp/linux/commits/Nuno-S-via-B4-Relay/dmaengine-Fix-possuible-use-after-free/20260329-160000
base:   b7560798466a07d9c3fb011698e92c335ab28baf
patch link:    https://lore.kernel.org/r/20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b%40analog.com
patch subject: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
:::::: branch date: 16 hours ago
:::::: commit date: 16 hours ago
config: loongarch-randconfig-r071-20260329 (https://download.01.org/0day-ci/archive/20260330/202603300705.jhDfInJk-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 15.2.0
smatch: v0.5.0-9004-gb810ac53

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <error27@gmail.com>
| Closes: https://lore.kernel.org/r/202603300705.jhDfInJk-lkp@intel.com/

smatch warnings:
drivers/dma/dma-axi-dmac.c:1342 axi_dmac_probe() warn: address of NULL pointer 'dmac'

vim +/dmac +1342 drivers/dma/dma-axi-dmac.c

e6cff0d5c22ce16 Nuno Sá            2026-03-27  1231  
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1232  static int axi_dmac_probe(struct platform_device *pdev)
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1233  {
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1234  	struct dma_device *dma_dev;
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1235  	struct axi_dmac *__dmac;
a5b982af953bcc8 Chuhong Yuan       2019-12-09  1236  	struct regmap *regmap;
b377e670bac558a Alexandru Ardelean 2020-08-25  1237  	unsigned int version;
238f68a08e19a61 Paul Cercueil      2023-12-15  1238  	u32 irq_mask = 0;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1239  	int ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1240  
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1241  	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1242  	if (!dmac)
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1243  		return -ENOMEM;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1244  
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1245  	dmac->irq = platform_get_irq(pdev, 0);
50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1246  	if (dmac->irq < 0)
50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1247  		return dmac->irq;
50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1248  	if (dmac->irq == 0)
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1249  		return -EINVAL;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1250  
4b23603a251d240 Tudor Ambarus      2022-11-10  1251  	dmac->base = devm_platform_ioremap_resource(pdev, 0);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1252  	if (IS_ERR(dmac->base))
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1253  		return PTR_ERR(dmac->base);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1254  
779a44831a4f646 Nuno Sa            2024-03-28  1255  	dmac->clk = devm_clk_get_enabled(&pdev->dev, NULL);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1256  	if (IS_ERR(dmac->clk))
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1257  		return PTR_ERR(dmac->clk);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1258  
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1259  	version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION);
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1260  
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1261  	if (version >= ADI_AXI_PCORE_VER(4, 3, 'a'))
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1262  		ret = axi_dmac_read_chan_config(&pdev->dev, dmac);
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1263  	else
06b6e88c7ecf48e Alexandru Ardelean 2020-08-25  1264  		ret = axi_dmac_parse_dt(&pdev->dev, dmac);
78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1265  
06b6e88c7ecf48e Alexandru Ardelean 2020-08-25  1266  	if (ret < 0)
779a44831a4f646 Nuno Sa            2024-03-28  1267  		return ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1268  
a88fdece44d40cd Alexandru Ardelean 2020-08-25  1269  	INIT_LIST_HEAD(&dmac->chan.active_descs);
a88fdece44d40cd Alexandru Ardelean 2020-08-25  1270  
921234e0c5d77b5 Lars-Peter Clausen 2019-03-08  1271  	dma_set_max_seg_size(&pdev->dev, UINT_MAX);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1272  
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1273  	dma_dev = &dmac->dma_dev;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1274  	dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1275  	dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask);
9a05045d2a681d3 Dragos Bogdan      2019-03-26  1276  	dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1277  	dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1278  	dma_dev->device_tx_status = dma_cookie_status;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1279  	dma_dev->device_issue_pending = axi_dmac_issue_pending;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1280  	dma_dev->device_prep_slave_sg = axi_dmac_prep_slave_sg;
74609e5686701ed Paul Cercueil      2024-06-20  1281  	dma_dev->device_prep_peripheral_dma_vec = axi_dmac_prep_peripheral_dma_vec;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1282  	dma_dev->device_prep_dma_cyclic = axi_dmac_prep_dma_cyclic;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1283  	dma_dev->device_prep_interleaved_dma = axi_dmac_prep_interleaved;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1284  	dma_dev->device_terminate_all = axi_dmac_terminate_all;
860dd64c4382709 Lars-Peter Clausen 2015-10-20  1285  	dma_dev->device_synchronize = axi_dmac_synchronize;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1286  	dma_dev->dev = &pdev->dev;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1287  	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1288  	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1289  	dma_dev->device_release = axi_dmac_release;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1290  	dma_dev->directions = BIT(dmac->chan.direction);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1291  	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
e97dc7435972d28 Paul Cercueil      2023-12-15  1292  	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1293  	INIT_LIST_HEAD(&dma_dev->channels);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1294  
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1295  	dmac->chan.vchan.desc_free = axi_dmac_desc_free;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1296  	vchan_init(&dmac->chan.vchan, dma_dev);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1297  
b377e670bac558a Alexandru Ardelean 2020-08-25  1298  	ret = axi_dmac_detect_caps(dmac, version);
b5d89905d0391de Lars-Peter Clausen 2019-05-16  1299  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1300  		return ret;
56009f0d2f54e4c Lars-Peter Clausen 2019-03-26  1301  
5b969bd1d9cdfc8 Alexandru Ardelean 2019-05-27  1302  	dma_dev->copy_align = (dmac->chan.address_align_mask + 1);
56009f0d2f54e4c Lars-Peter Clausen 2019-03-26  1303  
238f68a08e19a61 Paul Cercueil      2023-12-15  1304  	if (dmac->chan.hw_sg)
238f68a08e19a61 Paul Cercueil      2023-12-15  1305  		irq_mask |= AXI_DMAC_IRQ_SOT;
238f68a08e19a61 Paul Cercueil      2023-12-15  1306  
238f68a08e19a61 Paul Cercueil      2023-12-15  1307  	axi_dmac_write(dmac, AXI_DMAC_REG_IRQ_MASK, irq_mask);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1308  
9327c7e7539371c Mathias Tausen     2022-07-26  1309  	if (of_dma_is_coherent(pdev->dev.of_node)) {
9327c7e7539371c Mathias Tausen     2022-07-26  1310  		ret = axi_dmac_read(dmac, AXI_DMAC_REG_COHERENCY_DESC);
9327c7e7539371c Mathias Tausen     2022-07-26  1311  
9327c7e7539371c Mathias Tausen     2022-07-26  1312  		if (version < ADI_AXI_PCORE_VER(4, 4, 'a') ||
9327c7e7539371c Mathias Tausen     2022-07-26  1313  		    !AXI_DMAC_DST_COHERENT_GET(ret)) {
9327c7e7539371c Mathias Tausen     2022-07-26  1314  			dev_err(dmac->dma_dev.dev,
9327c7e7539371c Mathias Tausen     2022-07-26  1315  				"Coherent DMA not supported in hardware");
779a44831a4f646 Nuno Sa            2024-03-28  1316  			return -EINVAL;
9327c7e7539371c Mathias Tausen     2022-07-26  1317  		}
9327c7e7539371c Mathias Tausen     2022-07-26  1318  	}
9327c7e7539371c Mathias Tausen     2022-07-26  1319  
779a44831a4f646 Nuno Sa            2024-03-28  1320  	ret = dmaenginem_async_device_register(dma_dev);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1321  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1322  		return ret;
779a44831a4f646 Nuno Sa            2024-03-28  1323  
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1324  	/*
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1325  	 * From this point on, our dmac object has it's lifetime bounded with
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1326  	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1327  	 * no more automatic kfree(). Also note that dmac is now NULL so we
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1328  	 * need __dmac.
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1329  	 */
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1330  	__dmac = no_free_ptr(dmac);
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1331  	get_device(&pdev->dev);
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1332  
779a44831a4f646 Nuno Sa            2024-03-28  1333  	/*
779a44831a4f646 Nuno Sa            2024-03-28  1334  	 * Put the action in here so it get's done before unregistering the DMA
779a44831a4f646 Nuno Sa            2024-03-28  1335  	 * device.
779a44831a4f646 Nuno Sa            2024-03-28  1336  	 */
779a44831a4f646 Nuno Sa            2024-03-28  1337  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1338  				       &__dmac->chan.vchan.task);
779a44831a4f646 Nuno Sa            2024-03-28  1339  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1340  		return ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1341  
0e3b67b348b838d Lars-Peter Clausen 2015-08-20 @1342  	ret = of_dma_controller_register(pdev->dev.of_node,
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1343  		of_dma_xlate_by_chan_id, dma_dev);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1344  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1345  		return ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1346  
779a44831a4f646 Nuno Sa            2024-03-28  1347  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_free_dma_controller,
779a44831a4f646 Nuno Sa            2024-03-28  1348  				       pdev->dev.of_node);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1349  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1350  		return ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1351  
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1352  	/* So that we can mark the device as unbound and disable it */
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1353  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1354  	if (ret)
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1355  		return ret;
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1356  
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1357  	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1358  			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
779a44831a4f646 Nuno Sa            2024-03-28  1359  	if (ret)
779a44831a4f646 Nuno Sa            2024-03-28  1360  		return ret;
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1361  
e6cff0d5c22ce16 Nuno Sá            2026-03-27  1362  	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
a5b982af953bcc8 Chuhong Yuan       2019-12-09  1363  				       &axi_dmac_regmap_config);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1364  
779a44831a4f646 Nuno Sa            2024-03-28  1365  	return PTR_ERR_OR_ZERO(regmap);
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1366  }
0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1367  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-29 23:40 [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind kernel test robot
@ 2026-03-30 14:32 ` Nuno Sá
  2026-03-30 14:37   ` Dan Carpenter
  0 siblings, 1 reply; 8+ messages in thread
From: Nuno Sá @ 2026-03-30 14:32 UTC (permalink / raw)
  To: kernel test robot; +Cc: oe-kbuild, Dan Carpenter

On Mon, Mar 30, 2026 at 07:40:55AM +0800, kernel test robot wrote:
> BCC: lkp@intel.com
> CC: oe-kbuild-all@lists.linux.dev
> In-Reply-To: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
> References: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
> TO: "Nuno Sá via B4 Relay" <devnull+nuno.sa.analog.com@kernel.org>
> TO: dmaengine@vger.kernel.org
> TO: linux-kernel@vger.kernel.org
> CC: "Lars-Peter Clausen" <lars@metafoo.de>
> CC: Vinod Koul <vkoul@kernel.org>
> CC: Frank Li <Frank.Li@kernel.org>
> 
> Hi Nuno,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on b7560798466a07d9c3fb011698e92c335ab28baf]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/Nuno-S-via-B4-Relay/dmaengine-Fix-possuible-use-after-free/20260329-160000
> base:   b7560798466a07d9c3fb011698e92c335ab28baf
> patch link:    https://lore.kernel.org/r/20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b%40analog.com
> patch subject: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
> :::::: branch date: 16 hours ago
> :::::: commit date: 16 hours ago
> config: loongarch-randconfig-r071-20260329 (https://download.01.org/0day-ci/archive/20260330/202603300705.jhDfInJk-lkp@intel.com/config)
> compiler: loongarch64-linux-gcc (GCC) 15.2.0
> smatch: v0.5.0-9004-gb810ac53
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Reported-by: Dan Carpenter <error27@gmail.com>
> | Closes: https://lore.kernel.org/r/202603300705.jhDfInJk-lkp@intel.com/
> 
> smatch warnings:
> drivers/dma/dma-axi-dmac.c:1342 axi_dmac_probe() warn: address of NULL pointer 'dmac'

Am I missing something or is this a false positive?

- Nuno Sá

> 
> vim +/dmac +1342 drivers/dma/dma-axi-dmac.c
> 
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1231  
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1232  static int axi_dmac_probe(struct platform_device *pdev)
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1233  {
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1234  	struct dma_device *dma_dev;
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1235  	struct axi_dmac *__dmac;
> a5b982af953bcc8 Chuhong Yuan       2019-12-09  1236  	struct regmap *regmap;
> b377e670bac558a Alexandru Ardelean 2020-08-25  1237  	unsigned int version;
> 238f68a08e19a61 Paul Cercueil      2023-12-15  1238  	u32 irq_mask = 0;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1239  	int ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1240  
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1241  	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1242  	if (!dmac)
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1243  		return -ENOMEM;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1244  
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1245  	dmac->irq = platform_get_irq(pdev, 0);
> 50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1246  	if (dmac->irq < 0)
> 50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1247  		return dmac->irq;
> 50dc60a25597e10 Lars-Peter Clausen 2016-07-01  1248  	if (dmac->irq == 0)
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1249  		return -EINVAL;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1250  
> 4b23603a251d240 Tudor Ambarus      2022-11-10  1251  	dmac->base = devm_platform_ioremap_resource(pdev, 0);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1252  	if (IS_ERR(dmac->base))
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1253  		return PTR_ERR(dmac->base);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1254  
> 779a44831a4f646 Nuno Sa            2024-03-28  1255  	dmac->clk = devm_clk_get_enabled(&pdev->dev, NULL);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1256  	if (IS_ERR(dmac->clk))
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1257  		return PTR_ERR(dmac->clk);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1258  
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1259  	version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION);
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1260  
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1261  	if (version >= ADI_AXI_PCORE_VER(4, 3, 'a'))
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1262  		ret = axi_dmac_read_chan_config(&pdev->dev, dmac);
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1263  	else
> 06b6e88c7ecf48e Alexandru Ardelean 2020-08-25  1264  		ret = axi_dmac_parse_dt(&pdev->dev, dmac);
> 78a2f92e4c4a3b7 Alexandru Ardelean 2020-08-25  1265  
> 06b6e88c7ecf48e Alexandru Ardelean 2020-08-25  1266  	if (ret < 0)
> 779a44831a4f646 Nuno Sa            2024-03-28  1267  		return ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1268  
> a88fdece44d40cd Alexandru Ardelean 2020-08-25  1269  	INIT_LIST_HEAD(&dmac->chan.active_descs);
> a88fdece44d40cd Alexandru Ardelean 2020-08-25  1270  
> 921234e0c5d77b5 Lars-Peter Clausen 2019-03-08  1271  	dma_set_max_seg_size(&pdev->dev, UINT_MAX);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1272  
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1273  	dma_dev = &dmac->dma_dev;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1274  	dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1275  	dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask);
> 9a05045d2a681d3 Dragos Bogdan      2019-03-26  1276  	dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1277  	dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1278  	dma_dev->device_tx_status = dma_cookie_status;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1279  	dma_dev->device_issue_pending = axi_dmac_issue_pending;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1280  	dma_dev->device_prep_slave_sg = axi_dmac_prep_slave_sg;
> 74609e5686701ed Paul Cercueil      2024-06-20  1281  	dma_dev->device_prep_peripheral_dma_vec = axi_dmac_prep_peripheral_dma_vec;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1282  	dma_dev->device_prep_dma_cyclic = axi_dmac_prep_dma_cyclic;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1283  	dma_dev->device_prep_interleaved_dma = axi_dmac_prep_interleaved;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1284  	dma_dev->device_terminate_all = axi_dmac_terminate_all;
> 860dd64c4382709 Lars-Peter Clausen 2015-10-20  1285  	dma_dev->device_synchronize = axi_dmac_synchronize;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1286  	dma_dev->dev = &pdev->dev;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1287  	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1288  	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1289  	dma_dev->device_release = axi_dmac_release;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1290  	dma_dev->directions = BIT(dmac->chan.direction);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1291  	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> e97dc7435972d28 Paul Cercueil      2023-12-15  1292  	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1293  	INIT_LIST_HEAD(&dma_dev->channels);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1294  
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1295  	dmac->chan.vchan.desc_free = axi_dmac_desc_free;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1296  	vchan_init(&dmac->chan.vchan, dma_dev);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1297  
> b377e670bac558a Alexandru Ardelean 2020-08-25  1298  	ret = axi_dmac_detect_caps(dmac, version);
> b5d89905d0391de Lars-Peter Clausen 2019-05-16  1299  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1300  		return ret;
> 56009f0d2f54e4c Lars-Peter Clausen 2019-03-26  1301  
> 5b969bd1d9cdfc8 Alexandru Ardelean 2019-05-27  1302  	dma_dev->copy_align = (dmac->chan.address_align_mask + 1);
> 56009f0d2f54e4c Lars-Peter Clausen 2019-03-26  1303  
> 238f68a08e19a61 Paul Cercueil      2023-12-15  1304  	if (dmac->chan.hw_sg)
> 238f68a08e19a61 Paul Cercueil      2023-12-15  1305  		irq_mask |= AXI_DMAC_IRQ_SOT;
> 238f68a08e19a61 Paul Cercueil      2023-12-15  1306  
> 238f68a08e19a61 Paul Cercueil      2023-12-15  1307  	axi_dmac_write(dmac, AXI_DMAC_REG_IRQ_MASK, irq_mask);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1308  
> 9327c7e7539371c Mathias Tausen     2022-07-26  1309  	if (of_dma_is_coherent(pdev->dev.of_node)) {
> 9327c7e7539371c Mathias Tausen     2022-07-26  1310  		ret = axi_dmac_read(dmac, AXI_DMAC_REG_COHERENCY_DESC);
> 9327c7e7539371c Mathias Tausen     2022-07-26  1311  
> 9327c7e7539371c Mathias Tausen     2022-07-26  1312  		if (version < ADI_AXI_PCORE_VER(4, 4, 'a') ||
> 9327c7e7539371c Mathias Tausen     2022-07-26  1313  		    !AXI_DMAC_DST_COHERENT_GET(ret)) {
> 9327c7e7539371c Mathias Tausen     2022-07-26  1314  			dev_err(dmac->dma_dev.dev,
> 9327c7e7539371c Mathias Tausen     2022-07-26  1315  				"Coherent DMA not supported in hardware");
> 779a44831a4f646 Nuno Sa            2024-03-28  1316  			return -EINVAL;
> 9327c7e7539371c Mathias Tausen     2022-07-26  1317  		}
> 9327c7e7539371c Mathias Tausen     2022-07-26  1318  	}
> 9327c7e7539371c Mathias Tausen     2022-07-26  1319  
> 779a44831a4f646 Nuno Sa            2024-03-28  1320  	ret = dmaenginem_async_device_register(dma_dev);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1321  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1322  		return ret;
> 779a44831a4f646 Nuno Sa            2024-03-28  1323  
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1324  	/*
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1325  	 * From this point on, our dmac object has it's lifetime bounded with
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1326  	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1327  	 * no more automatic kfree(). Also note that dmac is now NULL so we
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1328  	 * need __dmac.
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1329  	 */
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1330  	__dmac = no_free_ptr(dmac);
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1331  	get_device(&pdev->dev);
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1332  
> 779a44831a4f646 Nuno Sa            2024-03-28  1333  	/*
> 779a44831a4f646 Nuno Sa            2024-03-28  1334  	 * Put the action in here so it get's done before unregistering the DMA
> 779a44831a4f646 Nuno Sa            2024-03-28  1335  	 * device.
> 779a44831a4f646 Nuno Sa            2024-03-28  1336  	 */
> 779a44831a4f646 Nuno Sa            2024-03-28  1337  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1338  				       &__dmac->chan.vchan.task);
> 779a44831a4f646 Nuno Sa            2024-03-28  1339  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1340  		return ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1341  
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20 @1342  	ret = of_dma_controller_register(pdev->dev.of_node,
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1343  		of_dma_xlate_by_chan_id, dma_dev);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1344  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1345  		return ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1346  
> 779a44831a4f646 Nuno Sa            2024-03-28  1347  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_free_dma_controller,
> 779a44831a4f646 Nuno Sa            2024-03-28  1348  				       pdev->dev.of_node);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1349  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1350  		return ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1351  
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1352  	/* So that we can mark the device as unbound and disable it */
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1353  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1354  	if (ret)
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1355  		return ret;
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1356  
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1357  	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1358  			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
> 779a44831a4f646 Nuno Sa            2024-03-28  1359  	if (ret)
> 779a44831a4f646 Nuno Sa            2024-03-28  1360  		return ret;
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1361  
> e6cff0d5c22ce16 Nuno Sá            2026-03-27  1362  	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
> a5b982af953bcc8 Chuhong Yuan       2019-12-09  1363  				       &axi_dmac_regmap_config);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1364  
> 779a44831a4f646 Nuno Sa            2024-03-28  1365  	return PTR_ERR_OR_ZERO(regmap);
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1366  }
> 0e3b67b348b838d Lars-Peter Clausen 2015-08-20  1367  
> 
> -- 
> 0-DAY CI Kernel Test Service
> https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-30 14:32 ` Nuno Sá
@ 2026-03-30 14:37   ` Dan Carpenter
  0 siblings, 0 replies; 8+ messages in thread
From: Dan Carpenter @ 2026-03-30 14:37 UTC (permalink / raw)
  To: Nuno Sá; +Cc: kernel test robot, oe-kbuild

On Mon, Mar 30, 2026 at 03:32:25PM +0100, Nuno Sá wrote:
> On Mon, Mar 30, 2026 at 07:40:55AM +0800, kernel test robot wrote:
> > BCC: lkp@intel.com
> > CC: oe-kbuild-all@lists.linux.dev
> > In-Reply-To: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
> > References: <20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b@analog.com>
> > TO: "Nuno Sá via B4 Relay" <devnull+nuno.sa.analog.com@kernel.org>
> > TO: dmaengine@vger.kernel.org
> > TO: linux-kernel@vger.kernel.org
> > CC: "Lars-Peter Clausen" <lars@metafoo.de>
> > CC: Vinod Koul <vkoul@kernel.org>
> > CC: Frank Li <Frank.Li@kernel.org>
> > 
> > Hi Nuno,
> > 
> > kernel test robot noticed the following build warnings:
> > 
> > [auto build test WARNING on b7560798466a07d9c3fb011698e92c335ab28baf]
> > 
> > url:    https://github.com/intel-lab-lkp/linux/commits/Nuno-S-via-B4-Relay/dmaengine-Fix-possuible-use-after-free/20260329-160000
> > base:   b7560798466a07d9c3fb011698e92c335ab28baf
> > patch link:    https://lore.kernel.org/r/20260327-dma-dmac-handle-vunmap-v2-3-021f95f0e87b%40analog.com
> > patch subject: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
> > :::::: branch date: 16 hours ago
> > :::::: commit date: 16 hours ago
> > config: loongarch-randconfig-r071-20260329 (https://download.01.org/0day-ci/archive/20260330/202603300705.jhDfInJk-lkp@intel.com/config)
> > compiler: loongarch64-linux-gcc (GCC) 15.2.0
> > smatch: v0.5.0-9004-gb810ac53
> > 
> > If you fix the issue in a separate patch/commit (i.e. not just a new version of
> > the same patch/commit), kindly add following tags
> > | Reported-by: kernel test robot <lkp@intel.com>
> > | Reported-by: Dan Carpenter <error27@gmail.com>
> > | Closes: https://lore.kernel.org/r/202603300705.jhDfInJk-lkp@intel.com/
> > 
> > smatch warnings:
> > drivers/dma/dma-axi-dmac.c:1342 axi_dmac_probe() warn: address of NULL pointer 'dmac'
> 
> Am I missing something or is this a false positive?
> 

It is a false positive, yes.  You're using lei to see unfiltered
results.  Normally I review the warnings and hit resend on the
warnings which might be real.

regards,
dan carpenter


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-27 16:58   ` Nuno Sá via B4 Relay
  (?)
@ 2026-03-30 15:22   ` Frank Li
  2026-03-31  8:46     ` Nuno Sá
  -1 siblings, 1 reply; 8+ messages in thread
From: Frank Li @ 2026-03-30 15:22 UTC (permalink / raw)
  To: Nuno Sá
  Cc: dmaengine, linux-kernel, Lars-Peter Clausen, Vinod Koul, Frank Li

On Fri, Mar 27, 2026 at 04:58:40PM +0000, Nuno Sá wrote:
> The DMA device lifetime can extend beyond the platform driver unbind if
> DMA channels are still referenced by client drivers. This leads to
> use-after-free when the devm-managed memory is freed on unbind but the
> DMA device callbacks still access it.
>
> Fix this by:
>  - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
> its lifetime is not tied to the platform device.
>  - Implementing the device_release callback that so that we can free
> the object when reference count gets to 0 (no users).
>  - Adding an 'unbound' flag protected by the vchan lock that is set
> during driver removal, preventing MMIO accesses after the device has been
> unbound.
>
> While at it, explicitly include spinlock.h given it was missing.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> ---

Not sure if it similar with
https://lore.kernel.org/dmaengine/20250903-v6-16-topic-sdma-v1-9-ac7bab629e8b@pengutronix.de/

It looks like miss device link between comsumer and provider.

Frank

>  drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 60 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> index 127c3cf80a0e..70d3ad7e7d37 100644
> --- a/drivers/dma/dma-axi-dmac.c
> +++ b/drivers/dma/dma-axi-dmac.c
> @@ -24,6 +24,7 @@
>  #include <linux/platform_device.h>
>  #include <linux/regmap.h>
>  #include <linux/slab.h>
> +#include <linux/spinlock.h>
>
>  #include <dt-bindings/dma/axi-dmac.h>
>
> @@ -174,6 +175,8 @@ struct axi_dmac {
>
>  	struct dma_device dma_dev;
>  	struct axi_dmac_chan chan;
> +
> +	bool unbound;
>  };
>
>  static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> @@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
>  		dma_dev);
>  }
>
> +static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
> +{
> +	return container_of(dev, struct axi_dmac, dma_dev);
> +}
> +
>  static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
>  {
>  	return container_of(c, struct axi_dmac_chan, vchan.chan);
> @@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
>  	LIST_HEAD(head);
>
>  	spin_lock_irqsave(&chan->vchan.lock, flags);
> -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> +	/*
> +	 * Only allow the MMIO access if the device is live. Otherwise still
> +	 * go for freeing the descriptors.
> +	 */
> +	if (!dmac->unbound)
> +		axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
>  	chan->next_desc = NULL;
>  	vchan_get_all_descriptors(&chan->vchan, &head);
>  	list_splice_tail_init(&chan->active_descs, &head);
> @@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
>  	if (chan->hw_sg)
>  		ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
>
> -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> -
>  	spin_lock_irqsave(&chan->vchan.lock, flags);
> +	if (dmac->unbound) {
> +		spin_unlock_irqrestore(&chan->vchan.lock, flags);
> +		return;
> +	}
> +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
>  	if (vchan_issue_pending(&chan->vchan))
>  		axi_dmac_start_transfer(chan);
>  	spin_unlock_irqrestore(&chan->vchan.lock, flags);
> @@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
>  	return 0;
>  }
>
> +static void axi_dmac_release(struct dma_device *dma_dev)
> +{
> +	struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
> +
> +	put_device(dma_dev->dev);
> +	kfree(dmac);
> +}
> +
>  static void axi_dmac_tasklet_kill(void *task)
>  {
>  	tasklet_kill(task);
> @@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
>  	of_dma_controller_free(of_node);
>  }
>
> +static void axi_dmac_disable(void *__dmac)
> +{
> +	struct axi_dmac *dmac = __dmac;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
> +	dmac->unbound = true;
> +	spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
> +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> +}
> +
>  static int axi_dmac_probe(struct platform_device *pdev)
>  {
>  	struct dma_device *dma_dev;
> -	struct axi_dmac *dmac;
> +	struct axi_dmac *__dmac;
>  	struct regmap *regmap;
>  	unsigned int version;
>  	u32 irq_mask = 0;
>  	int ret;
>
> -	dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
> +	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
>  	if (!dmac)
>  		return -ENOMEM;
>
> @@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
>  	dma_dev->dev = &pdev->dev;
>  	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
>  	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
> +	dma_dev->device_release = axi_dmac_release;
>  	dma_dev->directions = BIT(dmac->chan.direction);
>  	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
>  	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
> @@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
>  	if (ret)
>  		return ret;
>
> +	/*
> +	 * From this point on, our dmac object has it's lifetime bounded with
> +	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
> +	 * no more automatic kfree(). Also note that dmac is now NULL so we
> +	 * need __dmac.
> +	 */
> +	__dmac = no_free_ptr(dmac);
> +	get_device(&pdev->dev);
> +
>  	/*
>  	 * Put the action in here so it get's done before unregistering the DMA
>  	 * device.
>  	 */
>  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> -				       &dmac->chan.vchan.task);
> +				       &__dmac->chan.vchan.task);
>  	if (ret)
>  		return ret;
>
> @@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
>  	if (ret)
>  		return ret;
>
> -	ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
> -			       IRQF_SHARED, dev_name(&pdev->dev), dmac);
> +	/* So that we can mark the device as unbound and disable it */
> +	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
>  	if (ret)
>  		return ret;
>
> -	regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
> -		 &axi_dmac_regmap_config);
> +	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
> +			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
> +	if (ret)
> +		return ret;
> +
> +	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
> +				       &axi_dmac_regmap_config);
>
>  	return PTR_ERR_OR_ZERO(regmap);
>  }
>
> --
> 2.53.0
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-30 15:22   ` Frank Li
@ 2026-03-31  8:46     ` Nuno Sá
  2026-03-31 14:20       ` Frank Li
  0 siblings, 1 reply; 8+ messages in thread
From: Nuno Sá @ 2026-03-31  8:46 UTC (permalink / raw)
  To: Frank Li
  Cc: Nuno Sá, dmaengine, linux-kernel, Lars-Peter Clausen,
	Vinod Koul, Frank Li

On Mon, Mar 30, 2026 at 11:22:10AM -0400, Frank Li wrote:
> On Fri, Mar 27, 2026 at 04:58:40PM +0000, Nuno Sá wrote:
> > The DMA device lifetime can extend beyond the platform driver unbind if
> > DMA channels are still referenced by client drivers. This leads to
> > use-after-free when the devm-managed memory is freed on unbind but the
> > DMA device callbacks still access it.
> >
> > Fix this by:
> >  - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
> > its lifetime is not tied to the platform device.
> >  - Implementing the device_release callback that so that we can free
> > the object when reference count gets to 0 (no users).
> >  - Adding an 'unbound' flag protected by the vchan lock that is set
> > during driver removal, preventing MMIO accesses after the device has been
> > unbound.
> >
> > While at it, explicitly include spinlock.h given it was missing.
> >
> > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > ---
> 
> Not sure if it similar with
> https://lore.kernel.org/dmaengine/20250903-v6-16-topic-sdma-v1-9-ac7bab629e8b@pengutronix.de/
> 
> It looks like miss device link between comsumer and provider.

Well, it surely it's related. I mean, if we ensure the consumers are
gone through devlinks and nothing is left behind, then this patch is basically unneeded.
 
But, FWIW, my 2cents would also go into questioning if AUTOREMOVE is
really want we want in every situation? Might be to harsh to assume that
a DMA channel consumer is useless even if DMA is gone. Anyways, is there
a v2 already? I would be interested in following this one...

- Nuno Sá

> 
> Frank
> 
> >  drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
> >  1 file changed, 60 insertions(+), 10 deletions(-)
> >
> > diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> > index 127c3cf80a0e..70d3ad7e7d37 100644
> > --- a/drivers/dma/dma-axi-dmac.c
> > +++ b/drivers/dma/dma-axi-dmac.c
> > @@ -24,6 +24,7 @@
> >  #include <linux/platform_device.h>
> >  #include <linux/regmap.h>
> >  #include <linux/slab.h>
> > +#include <linux/spinlock.h>
> >
> >  #include <dt-bindings/dma/axi-dmac.h>
> >
> > @@ -174,6 +175,8 @@ struct axi_dmac {
> >
> >  	struct dma_device dma_dev;
> >  	struct axi_dmac_chan chan;
> > +
> > +	bool unbound;
> >  };
> >
> >  static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> > @@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> >  		dma_dev);
> >  }
> >
> > +static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
> > +{
> > +	return container_of(dev, struct axi_dmac, dma_dev);
> > +}
> > +
> >  static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
> >  {
> >  	return container_of(c, struct axi_dmac_chan, vchan.chan);
> > @@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
> >  	LIST_HEAD(head);
> >
> >  	spin_lock_irqsave(&chan->vchan.lock, flags);
> > -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > +	/*
> > +	 * Only allow the MMIO access if the device is live. Otherwise still
> > +	 * go for freeing the descriptors.
> > +	 */
> > +	if (!dmac->unbound)
> > +		axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> >  	chan->next_desc = NULL;
> >  	vchan_get_all_descriptors(&chan->vchan, &head);
> >  	list_splice_tail_init(&chan->active_descs, &head);
> > @@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
> >  	if (chan->hw_sg)
> >  		ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
> >
> > -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> > -
> >  	spin_lock_irqsave(&chan->vchan.lock, flags);
> > +	if (dmac->unbound) {
> > +		spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > +		return;
> > +	}
> > +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> >  	if (vchan_issue_pending(&chan->vchan))
> >  		axi_dmac_start_transfer(chan);
> >  	spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > @@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
> >  	return 0;
> >  }
> >
> > +static void axi_dmac_release(struct dma_device *dma_dev)
> > +{
> > +	struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
> > +
> > +	put_device(dma_dev->dev);
> > +	kfree(dmac);
> > +}
> > +
> >  static void axi_dmac_tasklet_kill(void *task)
> >  {
> >  	tasklet_kill(task);
> > @@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
> >  	of_dma_controller_free(of_node);
> >  }
> >
> > +static void axi_dmac_disable(void *__dmac)
> > +{
> > +	struct axi_dmac *dmac = __dmac;
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
> > +	dmac->unbound = true;
> > +	spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
> > +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > +}
> > +
> >  static int axi_dmac_probe(struct platform_device *pdev)
> >  {
> >  	struct dma_device *dma_dev;
> > -	struct axi_dmac *dmac;
> > +	struct axi_dmac *__dmac;
> >  	struct regmap *regmap;
> >  	unsigned int version;
> >  	u32 irq_mask = 0;
> >  	int ret;
> >
> > -	dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
> > +	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
> >  	if (!dmac)
> >  		return -ENOMEM;
> >
> > @@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
> >  	dma_dev->dev = &pdev->dev;
> >  	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
> >  	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
> > +	dma_dev->device_release = axi_dmac_release;
> >  	dma_dev->directions = BIT(dmac->chan.direction);
> >  	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> >  	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
> > @@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
> >  	if (ret)
> >  		return ret;
> >
> > +	/*
> > +	 * From this point on, our dmac object has it's lifetime bounded with
> > +	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
> > +	 * no more automatic kfree(). Also note that dmac is now NULL so we
> > +	 * need __dmac.
> > +	 */
> > +	__dmac = no_free_ptr(dmac);
> > +	get_device(&pdev->dev);
> > +
> >  	/*
> >  	 * Put the action in here so it get's done before unregistering the DMA
> >  	 * device.
> >  	 */
> >  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> > -				       &dmac->chan.vchan.task);
> > +				       &__dmac->chan.vchan.task);
> >  	if (ret)
> >  		return ret;
> >
> > @@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
> >  	if (ret)
> >  		return ret;
> >
> > -	ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
> > -			       IRQF_SHARED, dev_name(&pdev->dev), dmac);
> > +	/* So that we can mark the device as unbound and disable it */
> > +	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
> >  	if (ret)
> >  		return ret;
> >
> > -	regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
> > -		 &axi_dmac_regmap_config);
> > +	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
> > +			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
> > +	if (ret)
> > +		return ret;
> > +
> > +	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
> > +				       &axi_dmac_regmap_config);
> >
> >  	return PTR_ERR_OR_ZERO(regmap);
> >  }
> >
> > --
> > 2.53.0
> >

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind
  2026-03-31  8:46     ` Nuno Sá
@ 2026-03-31 14:20       ` Frank Li
  0 siblings, 0 replies; 8+ messages in thread
From: Frank Li @ 2026-03-31 14:20 UTC (permalink / raw)
  To: Nuno Sá
  Cc: Nuno Sá, dmaengine, linux-kernel, Lars-Peter Clausen,
	Vinod Koul, Frank Li

On Tue, Mar 31, 2026 at 09:46:35AM +0100, Nuno Sá wrote:
> On Mon, Mar 30, 2026 at 11:22:10AM -0400, Frank Li wrote:
> > On Fri, Mar 27, 2026 at 04:58:40PM +0000, Nuno Sá wrote:
> > > The DMA device lifetime can extend beyond the platform driver unbind if
> > > DMA channels are still referenced by client drivers. This leads to
> > > use-after-free when the devm-managed memory is freed on unbind but the
> > > DMA device callbacks still access it.
> > >
> > > Fix this by:
> > >  - Allocating axi_dmac with kzalloc_obj() instead of devm_kzalloc() so
> > > its lifetime is not tied to the platform device.
> > >  - Implementing the device_release callback that so that we can free
> > > the object when reference count gets to 0 (no users).
> > >  - Adding an 'unbound' flag protected by the vchan lock that is set
> > > during driver removal, preventing MMIO accesses after the device has been
> > > unbound.
> > >
> > > While at it, explicitly include spinlock.h given it was missing.
> > >
> > > Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> > > ---
> >
> > Not sure if it similar with
> > https://lore.kernel.org/dmaengine/20250903-v6-16-topic-sdma-v1-9-ac7bab629e8b@pengutronix.de/
> >
> > It looks like miss device link between comsumer and provider.
>
> Well, it surely it's related. I mean, if we ensure the consumers are
> gone through devlinks and nothing is left behind, then this patch is basically unneeded.
>
> But, FWIW, my 2cents would also go into questioning if AUTOREMOVE is
> really want we want in every situation? Might be to harsh to assume that
> a DMA channel consumer is useless even if DMA is gone. Anyways, is there
> a v2 already? I would be interested in following this one...

This patch may be missed by vnod, I have asked vnod to check again. The
open question link to channel device or dma enginee device. I prefer link
to channel devices, so it support more advance's runtime pm management.

Frank
>
> - Nuno Sá
>
> >
> > Frank
> >
> > >  drivers/dma/dma-axi-dmac.c | 70 +++++++++++++++++++++++++++++++++++++++-------
> > >  1 file changed, 60 insertions(+), 10 deletions(-)
> > >
> > > diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> > > index 127c3cf80a0e..70d3ad7e7d37 100644
> > > --- a/drivers/dma/dma-axi-dmac.c
> > > +++ b/drivers/dma/dma-axi-dmac.c
> > > @@ -24,6 +24,7 @@
> > >  #include <linux/platform_device.h>
> > >  #include <linux/regmap.h>
> > >  #include <linux/slab.h>
> > > +#include <linux/spinlock.h>
> > >
> > >  #include <dt-bindings/dma/axi-dmac.h>
> > >
> > > @@ -174,6 +175,8 @@ struct axi_dmac {
> > >
> > >  	struct dma_device dma_dev;
> > >  	struct axi_dmac_chan chan;
> > > +
> > > +	bool unbound;
> > >  };
> > >
> > >  static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> > > @@ -182,6 +185,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan)
> > >  		dma_dev);
> > >  }
> > >
> > > +static struct axi_dmac *dev_to_axi_dmac(struct dma_device *dev)
> > > +{
> > > +	return container_of(dev, struct axi_dmac, dma_dev);
> > > +}
> > > +
> > >  static struct axi_dmac_chan *to_axi_dmac_chan(struct dma_chan *c)
> > >  {
> > >  	return container_of(c, struct axi_dmac_chan, vchan.chan);
> > > @@ -614,7 +622,12 @@ static int axi_dmac_terminate_all(struct dma_chan *c)
> > >  	LIST_HEAD(head);
> > >
> > >  	spin_lock_irqsave(&chan->vchan.lock, flags);
> > > -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > > +	/*
> > > +	 * Only allow the MMIO access if the device is live. Otherwise still
> > > +	 * go for freeing the descriptors.
> > > +	 */
> > > +	if (!dmac->unbound)
> > > +		axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > >  	chan->next_desc = NULL;
> > >  	vchan_get_all_descriptors(&chan->vchan, &head);
> > >  	list_splice_tail_init(&chan->active_descs, &head);
> > > @@ -642,9 +655,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
> > >  	if (chan->hw_sg)
> > >  		ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
> > >
> > > -	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> > > -
> > >  	spin_lock_irqsave(&chan->vchan.lock, flags);
> > > +	if (dmac->unbound) {
> > > +		spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > +		return;
> > > +	}
> > > +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
> > >  	if (vchan_issue_pending(&chan->vchan))
> > >  		axi_dmac_start_transfer(chan);
> > >  	spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > @@ -1184,6 +1200,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
> > >  	return 0;
> > >  }
> > >
> > > +static void axi_dmac_release(struct dma_device *dma_dev)
> > > +{
> > > +	struct axi_dmac *dmac = dev_to_axi_dmac(dma_dev);
> > > +
> > > +	put_device(dma_dev->dev);
> > > +	kfree(dmac);
> > > +}
> > > +
> > >  static void axi_dmac_tasklet_kill(void *task)
> > >  {
> > >  	tasklet_kill(task);
> > > @@ -1194,16 +1218,27 @@ static void axi_dmac_free_dma_controller(void *of_node)
> > >  	of_dma_controller_free(of_node);
> > >  }
> > >
> > > +static void axi_dmac_disable(void *__dmac)
> > > +{
> > > +	struct axi_dmac *dmac = __dmac;
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&dmac->chan.vchan.lock, flags);
> > > +	dmac->unbound = true;
> > > +	spin_unlock_irqrestore(&dmac->chan.vchan.lock, flags);
> > > +	axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0);
> > > +}
> > > +
> > >  static int axi_dmac_probe(struct platform_device *pdev)
> > >  {
> > >  	struct dma_device *dma_dev;
> > > -	struct axi_dmac *dmac;
> > > +	struct axi_dmac *__dmac;
> > >  	struct regmap *regmap;
> > >  	unsigned int version;
> > >  	u32 irq_mask = 0;
> > >  	int ret;
> > >
> > > -	dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
> > > +	struct axi_dmac *dmac __free(kfree) = kzalloc_obj(struct axi_dmac);
> > >  	if (!dmac)
> > >  		return -ENOMEM;
> > >
> > > @@ -1251,6 +1286,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > >  	dma_dev->dev = &pdev->dev;
> > >  	dma_dev->src_addr_widths = BIT(dmac->chan.src_width);
> > >  	dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
> > > +	dma_dev->device_release = axi_dmac_release;
> > >  	dma_dev->directions = BIT(dmac->chan.direction);
> > >  	dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
> > >  	dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
> > > @@ -1285,12 +1321,21 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > >  	if (ret)
> > >  		return ret;
> > >
> > > +	/*
> > > +	 * From this point on, our dmac object has it's lifetime bounded with
> > > +	 * dma_dev. Will be freed when dma_dev refcount goes to 0. That means,
> > > +	 * no more automatic kfree(). Also note that dmac is now NULL so we
> > > +	 * need __dmac.
> > > +	 */
> > > +	__dmac = no_free_ptr(dmac);
> > > +	get_device(&pdev->dev);
> > > +
> > >  	/*
> > >  	 * Put the action in here so it get's done before unregistering the DMA
> > >  	 * device.
> > >  	 */
> > >  	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> > > -				       &dmac->chan.vchan.task);
> > > +				       &__dmac->chan.vchan.task);
> > >  	if (ret)
> > >  		return ret;
> > >
> > > @@ -1304,13 +1349,18 @@ static int axi_dmac_probe(struct platform_device *pdev)
> > >  	if (ret)
> > >  		return ret;
> > >
> > > -	ret = devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handler,
> > > -			       IRQF_SHARED, dev_name(&pdev->dev), dmac);
> > > +	/* So that we can mark the device as unbound and disable it */
> > > +	ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, __dmac);
> > >  	if (ret)
> > >  		return ret;
> > >
> > > -	regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base,
> > > -		 &axi_dmac_regmap_config);
> > > +	ret = devm_request_irq(&pdev->dev, __dmac->irq, axi_dmac_interrupt_handler,
> > > +			       IRQF_SHARED, dev_name(&pdev->dev), __dmac);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	regmap = devm_regmap_init_mmio(&pdev->dev, __dmac->base,
> > > +				       &axi_dmac_regmap_config);
> > >
> > >  	return PTR_ERR_OR_ZERO(regmap);
> > >  }
> > >
> > > --
> > > 2.53.0
> > >

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-03-31 14:20 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-29 23:40 [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind kernel test robot
2026-03-30 14:32 ` Nuno Sá
2026-03-30 14:37   ` Dan Carpenter
  -- strict thread matches above, loose matches on Subject: below --
2026-03-27 16:58 [PATCH v2 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá
2026-03-27 16:58 ` [PATCH v2 3/4] dmaengine: dma-axi-dmac: fix use-after-free on unbind Nuno Sá
2026-03-27 16:58   ` Nuno Sá via B4 Relay
2026-03-30 15:22   ` Frank Li
2026-03-31  8:46     ` Nuno Sá
2026-03-31 14:20       ` Frank Li

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.