bootflow_scan_first()/bootflow_scan_next() try each candidate
(bootdev, method, partition) in turn. When a candidate fails and is
not returned to the caller (no BOOTFLOWIF_ALL), the bootflow is simply
abandoned: the next candidate's bootflow_init() memsets the struct,
orphaning everything the failed attempt allocated, starting with the
name allocated in bootdev_find_in_blk().

Each failed candidate therefore leaks its allocations on every scan.
A single failing 'bootflow scan' on a sandbox MMC with a RAUC A/B
layout leaks about 1 KB across ~30 abandoned candidates, and scans can
be retried indefinitely from the U-Boot prompt.

Free the bootflow when it failed and is not passed back to the caller.

Add a check to the bootflow_rauc test that repeating a failing scan
does not change the number of allocated bytes. Together with the
previous RAUC fixes this makes the failed-scan path leak-free.

Fixes: a8f5be178db5 ("bootstd: Add support for bootflows")
Signed-off-by: Aristo Chen <[email protected]>
---
 boot/bootflow.c      | 2 ++
 test/boot/bootflow.c | 8 ++++++++
 2 files changed, 10 insertions(+)

diff --git a/boot/bootflow.c b/boot/bootflow.c
index d8a4a81a838..7ee940460af 100644
--- a/boot/bootflow.c
+++ b/boot/bootflow.c
@@ -580,6 +580,7 @@ int bootflow_scan_first(struct udevice *dev, const char 
*label,
                        if (iter->flags & BOOTFLOWIF_ALL)
                                return log_msg_ret("all", ret);
                }
+               bootflow_free(bflow);
                iter->err = ret;
                ret = bootflow_scan_next(iter, bflow);
                if (ret)
@@ -614,6 +615,7 @@ int bootflow_scan_next(struct bootflow_iter *iter, struct 
bootflow *bflow)
                                if (iter->flags & BOOTFLOWIF_ALL)
                                        return log_msg_ret("all", ret);
                        }
+                       bootflow_free(bflow);
                } else {
                        log_debug("incr failed, err=%d\n", ret);
                        iter->err = ret;
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index 8fa8835c20c..ef4638ec700 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -1403,6 +1403,7 @@ static int bootflow_rauc(struct unit_test_state *uts)
        struct udevice *bootstd;
        static const char *order[] = {NULL, NULL};
        const char **old_order;
+       ulong mem_start;
        ofnode root;
        ofnode node;
 
@@ -1458,6 +1459,13 @@ static int bootflow_rauc(struct unit_test_state *uts)
        ut_assert_skip_to_line("(0 bootflows, 0 valid)");
        ut_assert_console_end();
 
+       /* Repeating the failed scan must not leak memory */
+       mem_start = ut_check_delta(0);
+       ut_assertok(run_command("bootflow scan", 0));
+       ut_assert_nextline("No bootflows found; try again with -l");
+       ut_assert_console_end();
+       ut_asserteq(0, ut_check_delta(mem_start));
+
        /*
         * A failed scan with -a stores the failed bootflows; the next scan
         * removes them, freeing bootmeth_priv. This used to double free the
-- 
2.43.0

Reply via email to