summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-06-11 07:56:52 -0600
committerTom Rini <[email protected]>2026-06-11 07:56:52 -0600
commit69491eb60a86e68fac304a7f1f9ff9fcd05f0d39 (patch)
tree8782975ac5abaa7f4d09e03ea328ed364767f94c
parentd5dd2ebb5af37f84770f6397869fe80ff49a2030 (diff)
parentc8a636af67c640e1427e1085c8bada672e48f805 (diff)
Merge patch series "allow control DTB to double as "FIT image""
Rasmus Villemoes <[email protected]> says: The commit message for patch 1 explains what it is I'd like to be able to do, but here's some more background: For a long time, we've embedded the boot script in the U-Boot binary by building a bootscript.itb, and using a .dtsi like / { config { bootscript = /incbin/("/path/to/bootscript.itb"); }; }; which in turn is mentioned in CONFIG_DEVICE_TREE_INCLUDES, that bootscript.itb FIT image has been embedded in U-Boot's control dtb. Running that was then a matter of doing fdt addr ${fdtcontroladdr} && fdt get addr bsaddr /config bootscript && source ${bsaddr} There are a couple of advantage of having the bootscript (and other script logic) embedded in the U-Boot binary. First, there's no need to figure out some separate partition to store the script in, and making sure that gets updated whenever the bootloader itself does. Second, one doesn't need to worry about verifying the script; whatever steps one needs to take to implement secure boot for U-Boot itself will by necessity also cover the control dtb (if nothing else then because that's where the public key for the kernel verification lives). And third, the boot script is automatically updated together with U-Boot itself; and if U-Boot is stored in an eMMC boot partition, that update is guaranteed to be atomic. Now with the stricter requirements of libfdt starting from v2026.04, the above command no longer worked, or only half the time, because the embedded FIT image may not land on an 8-byte aligned address. So that line had to be changed a little (line breaks added) fdt addr ${fdtcontroladdr} && fdt get addr bsaddr /config bootscript && fdt get size bssize /config bootscript && cp.b ${bsaddr} ${loadaddr} ${bssize} && source ${loadaddr} which is getting quite unwieldy. Then it struck me that one could perhaps simplify all of this quite a lot: Cut out the intermediate bootscript.itb, just create a .dtsi which directly puts a /images node inside the control dtb / { images { default = "bootscript"; bootscript { description = "Boot script"; data = /incbin/("/path/to/bootscript.sh"); type = "script"; compression = "none"; }; }; }; and treat the control dtb itself as a FIT image; so the command to put in $bootcmd becomes simply source ${fdtcontroladdr}:bootscript and embedding other pieces of callable scripts is quite trivial. And that almost works out-of-the-box, except for the fit_check_format() sanity check. Introduce a CONFIG_ knob that allows one to opt out of those sanity checks, for the special case of the address being checked being identical to gd->fdt_blob. Link: https://lore.kernel.org/r/[email protected]
-rw-r--r--arch/sandbox/dts/sandbox-boot.sh2
-rw-r--r--arch/sandbox/dts/sandbox-inner.sh4
-rw-r--r--arch/sandbox/dts/sandbox-outer.sh4
-rw-r--r--arch/sandbox/dts/sandbox_scripts.dtsi24
-rw-r--r--boot/Kconfig13
-rw-r--r--boot/image-fit.c24
-rw-r--r--configs/sandbox_defconfig2
-rw-r--r--doc/develop/devicetree/control.rst58
-rw-r--r--test/py/tests/test_source.py31
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]('cmd_echo')
[email protected]('cmd_source')
[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]('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')