[PATCH 4.14 74/94] spi: Fix memory leak on splited transfers

From: Greg Kroah-Hartman
Date: Mon Sep 21 2020 - 13:00:38 EST

From: Gustav Wiklander <gustavwi@xxxxxxxx>

[ Upstream commit b59a7ca15464c78ea1ba3b280cfc5ac5ece11ade ]

In the prepare_message callback the bus driver has the
opportunity to split a transfer into smaller chunks.
spi_map_msg is done after prepare_message.

Function spi_res_release releases the splited transfers
in the message. Therefore spi_res_release should be called
after spi_map_msg.

The previous try at this was commit c9ba7a16d0f1
which released the splited transfers after
spi_finalize_current_message had been called.
This introduced a race since the message struct could be
out of scope because the spi_sync call got completed.

Fixes this leak on spi bus driver spi-bcm2835.c when transfer
size is greater than 65532:

spi_test_execute_msg+0x60/0x340 [spi_loopback_test]
spi_test_run_iter+0x548/0x578 [spi_loopback_test]
spi_test_run_test+0x94/0x140 [spi_loopback_test]
spi_test_run_tests+0x150/0x180 [spi_loopback_test]
spi_loopback_test_probe+0x50/0xd0 [spi_loopback_test]

Signed-off-by: Gustav Wiklander <gustavwi@xxxxxxxx>
Link: https://lore.kernel.org/r/20200908151129.15915-1-gustav.wiklander@xxxxxxxx
Signed-off-by: Mark Brown <broonie@xxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
drivers/spi/spi.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index ab6a4f85bcde7..acc8eeed73f07 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1125,8 +1125,6 @@ out:
if (msg->status && ctlr->handle_err)
ctlr->handle_err(ctlr, msg);

- spi_res_release(ctlr, msg);

return ret;
@@ -1384,6 +1382,13 @@ void spi_finalize_current_message(struct spi_controller *ctlr)

spi_unmap_msg(ctlr, mesg);

+ /* In the prepare_messages callback the spi bus has the opportunity to
+ * split a transfer to smaller chunks.
+ * Release splited transfers here since spi_map_msg is done on the
+ * splited transfers.
+ */
+ spi_res_release(ctlr, mesg);
if (ctlr->cur_msg_prepared && ctlr->unprepare_message) {
ret = ctlr->unprepare_message(ctlr, mesg);
if (ret) {