From nobody Thu Apr 2 21:58:56 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 44B192C21C2; Thu, 26 Mar 2026 13:37:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774532251; cv=none; b=IBg1koJr/G/8/5oN1izLiqQkpuwkHiIuxghPR+hYuyO1n1epBi/rpAM8UjhVPpgtKvUlY2iIK/foFWX4i5Wcdkreu3dGZCbf7+HLjq7OgwQBiZDxqT6eAc6Tg+MQEnDvZn4U3YrKPnJc0Tp85b0Fj6wpnOOFMGvT4OmuC4x+zCw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774532251; c=relaxed/simple; bh=6V7xCJfEpHcsI1kelRuOtMsngH7oGbsYsxC25INvh04=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BhIR2Npn3BHNxOmTuQR6Xrn9olXn3Ua7ztktX8KCvg53BcYkOGKW81Sm5m2iKEgKKG8tFArj4oyrmbtF7FDCSpj8tJiRgEP20yphAfOisQ9iyN0rWH/4QtZzF9XfZT1Yd1XeMF6ClpECQntCswFe3IzboEvZt5oyw9feCX/oaGw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RHBq9W3O; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="RHBq9W3O" Received: by smtp.kernel.org (Postfix) with ESMTPS id 1FAA3C116C6; Thu, 26 Mar 2026 13:37:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774532251; bh=6V7xCJfEpHcsI1kelRuOtMsngH7oGbsYsxC25INvh04=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=RHBq9W3OCscv5bMzBpdAotxFSWc54Dcwb/1PTX4+vSyKOB0rnaI1KoNDRATFdCnT4 kL49FKoM1EGKsLor3ajGW33XwRyTO6JDdIqDs4IQ1vIYp8SNXvuGQJi+1ASjQd99W8 sBZMvLWwk06vtuXISlWbzk+X5NaUl5ih2jUuC2CLQ6iuwAzTH6djhHEHH5nAa6Dfe5 btPrlEVQwsJ0kUFC6jYeHALR6ckPybNccEy8JQ80NWbbJUrN8xuVFbS5XX6NRQZZdk ysPsOTzlK92hXninJ8+2vegs4s5gJRVMr+IbPsHLOhRK8AilktAMzIQFLeODtbZ/Dp kyLChkV4XlEzQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 15FD210A62CF; Thu, 26 Mar 2026 13:37:31 +0000 (UTC) From: =?utf-8?q?Nuno_S=C3=A1_via_B4_Relay?= Date: Thu, 26 Mar 2026 13:37:36 +0000 Subject: [PATCH 2/2] dmaengine: dma-axi-dmac: fix use-after-free on unbind Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260326-dma-dmac-handle-vunmap-v1-2-be3e46ffaf69@analog.com> References: <20260326-dma-dmac-handle-vunmap-v1-0-be3e46ffaf69@analog.com> In-Reply-To: <20260326-dma-dmac-handle-vunmap-v1-0-be3e46ffaf69@analog.com> To: dmaengine@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Lars-Peter Clausen , Vinod Koul , Frank Li X-Mailer: b4 0.15.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774532297; l=4725; i=nuno.sa@analog.com; s=20231116; h=from:subject:message-id; bh=3oOip9GOTG6Og9wQ6FNEHbQ61iic0c2QfMR92xEdP2o=; b=/dP6+C3Zf6eLzl6gGhE28fBFM+vhQkt8yiwTx/pdRhKjPAKIBDlZ7VIyFRbvVpjz1pKBlMqRr QMBcTIqs95RDMJXuuvW5INrDOvaTyr8K6AxCH6rt7KpIpptsHBFS+hH X-Developer-Key: i=nuno.sa@analog.com; a=ed25519; pk=3NQwYA013OUYZsmDFBf8rmyyr5iQlxV/9H4/Df83o1E= X-Endpoint-Received: by B4 Relay for nuno.sa@analog.com/20231116 with auth_id=100 X-Original-From: =?utf-8?q?Nuno_S=C3=A1?= Reply-To: nuno.sa@analog.com From: Nuno S=C3=A1 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. Signed-off-by: Nuno S=C3=A1 --- drivers/dma/dma-axi-dmac.c | 47 ++++++++++++++++++++++++++++++++++++++++++= ---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index df2668064ea2..99454e096588 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -176,6 +176,8 @@ struct axi_dmac { =20 struct dma_device dma_dev; struct axi_dmac_chan chan; + + bool unbound; }; =20 static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan) @@ -184,6 +186,11 @@ static struct axi_dmac *chan_to_axi_dmac(struct axi_dm= ac_chan *chan) dma_dev); } =20 +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); @@ -616,6 +623,11 @@ static int axi_dmac_terminate_all(struct dma_chan *c) LIST_HEAD(head); =20 spin_lock_irqsave(&chan->vchan.lock, flags); + if (dmac->unbound) { + /* We're gone */ + spin_unlock_irqrestore(&chan->vchan.lock, flags); + return -ENODEV; + } axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0); chan->next_desc =3D NULL; vchan_get_all_descriptors(&chan->vchan, &head); @@ -644,9 +656,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c) if (chan->hw_sg) ctrl |=3D AXI_DMAC_CTRL_ENABLE_SG; =20 - 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); @@ -1206,6 +1221,14 @@ static int axi_dmac_detect_caps(struct axi_dmac *dma= c, unsigned int version) return 0; } =20 +static void axi_dmac_release(struct dma_device *dma_dev) +{ + struct axi_dmac *dmac =3D dev_to_axi_dmac(dma_dev); + + put_device(dma_dev->dev); + kfree(dmac); +} + static void axi_dmac_tasklet_kill(void *task) { tasklet_kill(task); @@ -1216,6 +1239,16 @@ static void axi_dmac_free_dma_controller(void *of_no= de) of_dma_controller_free(of_node); } =20 +static void axi_dmac_disable(void *__dmac) +{ + struct axi_dmac *dmac =3D __dmac; + + spin_lock(&dmac->chan.vchan.lock); + dmac->unbound =3D true; + spin_unlock(&dmac->chan.vchan.lock); + axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, 0); +} + static int axi_dmac_probe(struct platform_device *pdev) { struct dma_device *dma_dev; @@ -1225,7 +1258,7 @@ static int axi_dmac_probe(struct platform_device *pde= v) u32 irq_mask =3D 0; int ret; =20 - dmac =3D devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL); + dmac =3D kzalloc_obj(struct axi_dmac); if (!dmac) return -ENOMEM; =20 @@ -1270,9 +1303,10 @@ static int axi_dmac_probe(struct platform_device *pd= ev) dma_dev->device_prep_interleaved_dma =3D axi_dmac_prep_interleaved; dma_dev->device_terminate_all =3D axi_dmac_terminate_all; dma_dev->device_synchronize =3D axi_dmac_synchronize; - dma_dev->dev =3D &pdev->dev; + dma_dev->dev =3D get_device(&pdev->dev); dma_dev->src_addr_widths =3D BIT(dmac->chan.src_width); dma_dev->dst_addr_widths =3D BIT(dmac->chan.dest_width); + dma_dev->device_release =3D axi_dmac_release; dma_dev->directions =3D BIT(dmac->chan.direction); dma_dev->residue_granularity =3D DMA_RESIDUE_GRANULARITY_DESCRIPTOR; dma_dev->max_sg_burst =3D 31; /* 31 SGs maximum in one burst */ @@ -1326,6 +1360,11 @@ static int axi_dmac_probe(struct platform_device *pd= ev) if (ret) return ret; =20 + /* So that we can mark the device as unbound and disable it */ + ret =3D devm_add_action_or_reset(&pdev->dev, axi_dmac_disable, dmac); + if (ret) + return ret; + ret =3D devm_request_irq(&pdev->dev, dmac->irq, axi_dmac_interrupt_handle= r, IRQF_SHARED, dev_name(&pdev->dev), dmac); if (ret) --=20 2.53.0