dmaengine: imx-sdma: fix use-after-free on probe error path [Linux 4.9.187]

This Linux kernel change "dmaengine: imx-sdma: fix use-after-free on probe error path" is included in the Linux 4.9.187 release. This change is authored by Sven Van Asbroeck <thesven73 [at] gmail.com> on Mon Jun 24 10:07:31 2019 -0400. The commit for this change in Linux stable tree is 9b8a4a1 (patch) which is from upstream commit 2b8066c. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 2b8066c.

dmaengine: imx-sdma: fix use-after-free on probe error path

[ Upstream commit 2b8066c3deb9140fdf258417a51479b2aeaa7622 ]

If probe() fails anywhere beyond the point where
sdma_get_firmware() is called, then a kernel oops may occur.

Problematic sequence of events:
1. probe() calls sdma_get_firmware(), which schedules the
   firmware callback to run when firmware becomes available,
   using the sdma instance structure as the context
2. probe() encounters an error, which deallocates the
   sdma instance structure
3. firmware becomes available, firmware callback is
   called with deallocated sdma instance structure
4. use after free - kernel oops !

Solution: only attempt to load firmware when we're certain
that probe() will succeed. This guarantees that the firmware
callback's context will remain valid.

Note that the remove() path is unaffected by this issue: the
firmware loader will increment the driver module's use count,
ensuring that the module cannot be unloaded while the
firmware callback is pending or running.

Signed-off-by: Sven Van Asbroeck <[email protected]>
Reviewed-by: Robin Gong <[email protected]>
[vkoul: fixed braces for if condition]
Signed-off-by: Vinod Koul <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>

There are 48 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/dma/imx-sdma.c | 48 +++++++++++++++++++++++++++---------------------
 1 file changed, 27 insertions(+), 21 deletions(-)

diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 84856ac..9f240b2 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -1821,27 +1821,6 @@ static int sdma_probe(struct platform_device *pdev)
    if (pdata && pdata->script_addrs)
        sdma_add_scripts(sdma, pdata->script_addrs);

-   if (pdata) {
-       ret = sdma_get_firmware(sdma, pdata->fw_name);
-       if (ret)
-           dev_warn(&pdev->dev, "failed to get firmware from platform data\n");
-   } else {
-       /*
-        * Because that device tree does not encode ROM script address,
-        * the RAM script in firmware is mandatory for device tree
-        * probe, otherwise it fails.
-        */
-       ret = of_property_read_string(np, "fsl,sdma-ram-script-name",
-                         &fw_name);
-       if (ret)
-           dev_warn(&pdev->dev, "failed to get firmware name\n");
-       else {
-           ret = sdma_get_firmware(sdma, fw_name);
-           if (ret)
-               dev_warn(&pdev->dev, "failed to get firmware from device tree\n");
-       }
-   }
-
    sdma->dma_device.dev = &pdev->dev;

    sdma->dma_device.device_alloc_chan_resources = sdma_alloc_chan_resources;
@@ -1883,6 +1862,33 @@ static int sdma_probe(struct platform_device *pdev)
        of_node_put(spba_bus);
    }

+   /*
+    * Kick off firmware loading as the very last step:
+    * attempt to load firmware only if we're not on the error path, because
+    * the firmware callback requires a fully functional and allocated sdma
+    * instance.
+    */
+   if (pdata) {
+       ret = sdma_get_firmware(sdma, pdata->fw_name);
+       if (ret)
+           dev_warn(&pdev->dev, "failed to get firmware from platform data\n");
+   } else {
+       /*
+        * Because that device tree does not encode ROM script address,
+        * the RAM script in firmware is mandatory for device tree
+        * probe, otherwise it fails.
+        */
+       ret = of_property_read_string(np, "fsl,sdma-ram-script-name",
+                         &fw_name);
+       if (ret) {
+           dev_warn(&pdev->dev, "failed to get firmware name\n");
+       } else {
+           ret = sdma_get_firmware(sdma, fw_name);
+           if (ret)
+               dev_warn(&pdev->dev, "failed to get firmware from device tree\n");
+       }
+   }
+
    return 0;

 err_register:

Leave a Reply

Your email address will not be published. Required fields are marked *