diff options
| -rw-r--r-- | arch/sandbox/dts/sandbox-boot.sh | 2 | ||||
| -rw-r--r-- | arch/sandbox/dts/sandbox-inner.sh | 4 | ||||
| -rw-r--r-- | arch/sandbox/dts/sandbox-outer.sh | 4 | ||||
| -rw-r--r-- | arch/sandbox/dts/sandbox_scripts.dtsi | 24 | ||||
| -rw-r--r-- | boot/Kconfig | 13 | ||||
| -rw-r--r-- | boot/image-fit.c | 24 | ||||
| -rw-r--r-- | configs/sandbox_defconfig | 2 | ||||
| -rw-r--r-- | doc/develop/devicetree/control.rst | 58 | ||||
| -rw-r--r-- | test/py/tests/test_source.py | 31 |
9 files changed, 156 insertions, 6 deletions
diff --git a/arch/sandbox/dts/sandbox-boot.sh b/arch/sandbox/dts/sandbox-boot.sh new file mode 100644 index 00000000000..4f7fa661151 --- /dev/null +++ b/arch/sandbox/dts/sandbox-boot.sh @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) +echo "* default script" diff --git a/arch/sandbox/dts/sandbox-inner.sh b/arch/sandbox/dts/sandbox-inner.sh new file mode 100644 index 00000000000..b8fc8f7484b --- /dev/null +++ b/arch/sandbox/dts/sandbox-inner.sh @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +# Some comment. +echo "* inner" diff --git a/arch/sandbox/dts/sandbox-outer.sh b/arch/sandbox/dts/sandbox-outer.sh new file mode 100644 index 00000000000..40294085433 --- /dev/null +++ b/arch/sandbox/dts/sandbox-outer.sh @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) +echo "* outer 1" +source ${fdtcontroladdr}:inner +echo "* outer 2" diff --git a/arch/sandbox/dts/sandbox_scripts.dtsi b/arch/sandbox/dts/sandbox_scripts.dtsi new file mode 100644 index 00000000000..c800ec39e87 --- /dev/null +++ b/arch/sandbox/dts/sandbox_scripts.dtsi @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/ { + images { + default = "boot"; + boot { + description = "Test boot script"; + data = /incbin/("sandbox-boot.sh"); + type = "script"; + compression = "none"; + }; + outer { + description = "Script testing recursion"; + data = /incbin/("sandbox-outer.sh"); + type = "script"; + compression = "none"; + }; + inner { + description = "Another test script"; + data = /incbin/("sandbox-inner.sh"); + type = "script"; + compression = "none"; + }; + }; +}; diff --git a/boot/Kconfig b/boot/Kconfig index e1114aea843..e6927d60b7b 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -103,6 +103,19 @@ config FIT_FULL_CHECK of bugs or omissions in the code. This includes a bad structure, multiple root nodes and the like. +config CONTROL_DTB_AS_FIT + bool "Allow U-Boot's control DTB to act as FIT image" + help + Enable this to exempt U-Boot's control DTB from the sanity + checks done to ensure FIT images are valid. This can for + example be used to embed whole scripts in the control DTB, + that can then be invoked using 'source ${fdtcontroladdr}'. + In a secure boot setup, this is safe, as the control DTB is + necessarily covered by any mechanism verifying U-Boot and + can therefore be trusted. This only affects the case where + the image being checked is gd->fdt_blob. See + doc/develop/devicetree/control.rst for details. + config FIT_SIGNATURE bool "Enable signature verification of FIT uImages" depends on DM diff --git a/boot/image-fit.c b/boot/image-fit.c index 1cf8e7be957..773eb1857c5 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -1658,6 +1658,16 @@ static int fdt_check_no_at(const void *fit, int parent) return 0; } +static int fit_check_images_node(const void *fit) +{ + if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) { + log_debug("Wrong FIT format: no images parent node\n"); + return -ENOENT; + } + + return 0; +} + int fit_check_format(const void *fit, ulong size) { int ret; @@ -1670,6 +1680,13 @@ int fit_check_format(const void *fit, ulong size) return -ENOEXEC; } + /* + * For the control DTB to act as a FIT image, we only require + * an /images node. + */ + if (CONFIG_IS_ENABLED(CONTROL_DTB_AS_FIT) && fit == gd_fdt_blob()) + return fit_check_images_node(fit); + if (CONFIG_IS_ENABLED(FIT_FULL_CHECK)) { /* * If we are not given the size, make do with calculating it. @@ -1718,12 +1735,7 @@ int fit_check_format(const void *fit, ulong size) } /* mandatory subimages parent '/images' node */ - if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) { - log_debug("Wrong FIT format: no images parent node\n"); - return -ENOENT; - } - - return 0; + return fit_check_images_node(fit); } int fit_conf_find_compat(const void *fit, const void *fdt) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ff4a6eb285a..512dbd6a0c6 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -20,6 +20,7 @@ CONFIG_EFI_CAPSULE_AUTHENTICATE=y CONFIG_EFI_CAPSULE_CRT_FILE="board/sandbox/capsule_pub_key_good.crt" CONFIG_BUTTON_CMD=y CONFIG_FIT=y +CONFIG_CONTROL_DTB_AS_FIT=y CONFIG_FIT_SIGNATURE=y CONFIG_FIT_CIPHER=y CONFIG_FIT_VERITY=y @@ -153,6 +154,7 @@ CONFIG_CMD_STACKPROTECTOR_TEST=y CONFIG_CMD_SPAWN=y CONFIG_MAC_PARTITION=y CONFIG_OF_LIVE=y +CONFIG_DEVICE_TREE_INCLUDES="sandbox_scripts.dtsi" CONFIG_ENV_IS_NOWHERE=y CONFIG_ENV_IS_IN_FAT=y CONFIG_ENV_IS_IN_EXT4=y diff --git a/doc/develop/devicetree/control.rst b/doc/develop/devicetree/control.rst index 634114af59a..7d6117d5c4b 100644 --- a/doc/develop/devicetree/control.rst +++ b/doc/develop/devicetree/control.rst @@ -232,6 +232,64 @@ outside the U-Boot repository. You can use `DEVICE_TREE_INCLUDES` Kconfig option to specify a list of .dtsi files that will also be included when building .dtb files. +Scripts embedded in control DTB +------------------------------- + +The `DEVICE_TREE_INCLUDES` option can also be used to make the control +DTB serve double duty as a FIT image. By including a `scripts.dtsi` +file containing something like:: + + / { + images { + default = "boot"; + boot { + description = "Bootscript"; + data = /incbin/("boot.sh"); + type = "script"; + compression = "none"; + }; + factory-reset { + description = "Script for performing factory reset"; + data = /incbin/("factory-reset.sh"); + type = "script"; + compression = "none"; + }; + }; + }; + +one can call those scripts using the `source` command in the U-Boot shell:: + + source ${fdtcontroladdr}:boot + +or just ``source ${fdtcontroladdr}`` for invoking the default. + +Since one does not need to separately build a "real" FIT image +containing those scripts, this simplifies both the build process and +the boot logic, as the latter does not need to first load the FIT +image from storage. + +Another advantage is that when the bootloader and boot script must be +updated together, it is easier to achieve a guaranteed atomic update +when the boot script is embedded inside the U-Boot binary, instead of +stored separately. + +For the above to work, one must enable the `CONTROL_DTB_AS_FIT` config +option, which will (when the address passed to the `source` command is +the address of U-Boot's control DTB) elide certain sanity checks that +are normally done: With the above `.dtsi` snippet, the control DTB +does not quite become a "real" FIT image - it lacks `timestamp` and +`description` properties, but more importantly, FIT images cannot +contain nodes with `@` in their names (unit addresses) anywhere, and +the control DTB obviously does have such nodes. + +This is not a security problem, as the control DTB is necessarily +trusted. In any secure boot setup where the bootloader is verified, +that mechanism must also include verification of the control DTB. So +in fact, since the scripts embedded this way are then also +automatically verified, it simplifies implementation of secure +boot. When using a separate FIT image, one must build it with +appropriate signatures, just as when building a FIT image containing a +kernel/dtb/initramfs. Devicetree bindings schema checks --------------------------------- diff --git a/test/py/tests/test_source.py b/test/py/tests/test_source.py index 970d8c79869..29ab804f81b 100644 --- a/test/py/tests/test_source.py +++ b/test/py/tests/test_source.py @@ -34,3 +34,34 @@ def test_source(ubman): ubman.run_command('fdt rm /images default') assert 'Fail' in ubman.run_command('source || echo Fail') assert 'Fail' in ubman.run_command('source \\# || echo Fail') + [email protected]('sandbox') [email protected]('cmd_echo') [email protected]('cmd_source') [email protected]('fit') [email protected]('control_dtb_as_fit') +def test_source_control_dtb(ubman): + output = ubman.run_command('source ${fdtcontroladdr}') + assert '* default script' in output + + output = ubman.run_command('source ${fdtcontroladdr}:boot') + assert '* default script' in output + + output = ubman.run_command('source ${fdtcontroladdr}:outer') + assert '* outer 1' in output + assert '* inner' in output + assert '* outer 2' in output + + output = ubman.run_command('source ${fdtcontroladdr}:inner') + assert '* outer' not in output + assert '* inner' in output + + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr}:no-such-script || echo Fail') + [email protected]('cmd_echo') [email protected]('cmd_source') [email protected]('fit') [email protected]('control_dtb_as_fit') +def test_source_reject_control_dtb(ubman): + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr} || echo Fail') + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr}:boot || echo Fail') |
