diff mbox series

[2/3] bootstd: Probe bootmeth devices for bootmeths env var

Message ID 20250112034213.13153-3-semen.protsenko@linaro.org
State New
Headers show
Series bootstd: Fix efi_mgr usage in bootmeths env var | expand

Commit Message

Sam Protsenko Jan. 12, 2025, 3:42 a.m. UTC
Specifying efi_mgr in 'bootmeths' environment variable leads to NULL
pointer dereference when 'bootflow scan' is executed, with call trace
like this:

    priv->fake_dev // NULL pointer dereference
    .read_bootflow = efi_mgr_read_bootflow()
    bootmeth_get_bootflow()
    bootflow_check()
    bootflow_scan_first()
    do_bootflow_scan()
    'bootflow scan -l'

That happens because in case when 'bootmeths' env var is defined the
bootmeth_efi_mgr driver is not probed, and the memory for its private
data isn't allocated by .priv_auto. In case when 'bootmeths' env var is
not defined, the std->bootmeth_count is 0, and the execution flow in
bootmeth_setup_iter_order() takes "no ordering" path, which in turn runs
uclass_get_device_by_seq() -> ... -> device_probe(), so issue isn't
present there. But when 'bootmeths' is defined and contains efi_mgr, the
std->bootmeth_count > 0, so bootmeth_setup_iter_order() follows the "we
have an ordering" path, where devices are not probed. In other words:

    'bootmeths' defined           'bootmeths' not defined
    --------------------------------------------------------
    priv == NULL                    priv != NULL
         ^                                ^
         |                        device_alloc_priv()
     no probe                     device_of_to_plat()
         ^                        device_probe()
         |                        uclass_get_device_tail()
    dev = order[i]                uclass_get_device_by_seq()
         ^                                ^
         | have an ordering               | no ordering
         +----------------+---------------+
                          |
             bootmeth_setup_iter_order()
             bootflow_scan_first()
             do_bootflow_scan()

Add an explicit device_probe() call in "we have an ordering" case to fix
the issue.

Fixes: c627cfc14c08 ("bootstd: Allow scanning for global bootmeths separately")
Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
---
 boot/bootmeth-uclass.c | 7 +++++++
 1 file changed, 7 insertions(+)

Comments

Heinrich Schuchardt Jan. 13, 2025, 7:40 a.m. UTC | #1
On 1/12/25 04:42, Sam Protsenko wrote:
> Specifying efi_mgr in 'bootmeths' environment variable leads to NULL
> pointer dereference when 'bootflow scan' is executed, with call trace
> like this:
>
>      priv->fake_dev // NULL pointer dereference
>      .read_bootflow = efi_mgr_read_bootflow()
>      bootmeth_get_bootflow()
>      bootflow_check()
>      bootflow_scan_first()
>      do_bootflow_scan()
>      'bootflow scan -l'
>
> That happens because in case when 'bootmeths' env var is defined the
> bootmeth_efi_mgr driver is not probed, and the memory for its private
> data isn't allocated by .priv_auto. In case when 'bootmeths' env var is
> not defined, the std->bootmeth_count is 0, and the execution flow in
> bootmeth_setup_iter_order() takes "no ordering" path, which in turn runs
> uclass_get_device_by_seq() -> ... -> device_probe(), so issue isn't
> present there. But when 'bootmeths' is defined and contains efi_mgr, the
> std->bootmeth_count > 0, so bootmeth_setup_iter_order() follows the "we
> have an ordering" path, where devices are not probed. In other words:
>
>      'bootmeths' defined           'bootmeths' not defined
>      --------------------------------------------------------
>      priv == NULL                    priv != NULL
>           ^                                ^
>           |                        device_alloc_priv()
>       no probe                     device_of_to_plat()
>           ^                        device_probe()
>           |                        uclass_get_device_tail()
>      dev = order[i]                uclass_get_device_by_seq()
>           ^                                ^
>           | have an ordering               | no ordering
>           +----------------+---------------+
>                            |
>               bootmeth_setup_iter_order()
>               bootflow_scan_first()
>               do_bootflow_scan()
>
> Add an explicit device_probe() call in "we have an ordering" case to fix
> the issue.
>
> Fixes: c627cfc14c08 ("bootstd: Allow scanning for global bootmeths separately")
> Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
> ---
>   boot/bootmeth-uclass.c | 7 +++++++
>   1 file changed, 7 insertions(+)
>
> diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
> index ff36da78d5a1..049389403191 100644
> --- a/boot/bootmeth-uclass.c
> +++ b/boot/bootmeth-uclass.c
> @@ -11,6 +11,7 @@
>   #include <bootmeth.h>
>   #include <bootstd.h>
>   #include <dm.h>
> +#include <dm/device-internal.h>
>   #include <env_internal.h>
>   #include <fs.h>
>   #include <malloc.h>
> @@ -146,6 +147,12 @@ int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global)
>   				struct bootmeth_uc_plat *ucp;
>   				bool is_global;
>
> +				ret = device_probe(dev);
> +				if (ret) {
> +					ret = log_msg_ret("probe", ret);
> +					goto err_order;
> +				}

This would mean that we fail boot if probing one of the boot devices
fails. Shouldn't we remove the device from the order instead?

Best regards

Heinrich

> +
>   				ucp = dev_get_uclass_plat(dev);
>   				is_global = ucp->flags &
>   					BOOTMETHF_GLOBAL;
diff mbox series

Patch

diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
index ff36da78d5a1..049389403191 100644
--- a/boot/bootmeth-uclass.c
+++ b/boot/bootmeth-uclass.c
@@ -11,6 +11,7 @@ 
 #include <bootmeth.h>
 #include <bootstd.h>
 #include <dm.h>
+#include <dm/device-internal.h>
 #include <env_internal.h>
 #include <fs.h>
 #include <malloc.h>
@@ -146,6 +147,12 @@  int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global)
 				struct bootmeth_uc_plat *ucp;
 				bool is_global;
 
+				ret = device_probe(dev);
+				if (ret) {
+					ret = log_msg_ret("probe", ret);
+					goto err_order;
+				}
+
 				ucp = dev_get_uclass_plat(dev);
 				is_global = ucp->flags &
 					BOOTMETHF_GLOBAL;