* [PATCH v3 1/5] boot: fit: tell callers when a devicetree buffer must be freed
2026-10-26 12:18 [PATCH v3 0/5] Prepare devicetrees from FIT configurations with fdt fit Carlo Caione
@ 2026-10-26 12:18 ` Carlo Caione
2026-11-09 17:27 ` David Lechner
2026-10-26 12:18 ` [PATCH v3 2/5] boot: fit: reject configurations with missing or invalid overlays Carlo Caione
` (3 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Carlo Caione @ 2026-10-26 12:18 UTC (permalink / raw)
To: u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, David Lechner, Ahmad Fatoum, Julien Masson,
Vitor Sato Eschholz, Randolph Sapp, Anton Ivanov, Daniel Golle,
Aristo Chen, Jonas Karlman, Jerome Forissier, Johan Jonker,
James Hilliard, Alexey Charkov, Vincent Jardin, Peter Robinson,
Carlo Caione
The FIT devicetree loader can return either data inside the FIT or a
newly allocated buffer. Callers which only need a temporary result
cannot tell whether it is safe to free.
Report when the returned buffer must be freed. This lets the new
fdt fit command copy the result to its chosen address without leaking
memory or freeing part of the original FIT. Also release temporary
allocations when they are no longer needed, including on error.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
boot/image-fdt.c | 3 ++-
boot/image-fit.c | 68 +++++++++++++++++++++++++++++++++++++++++++++-----------
include/image.h | 5 ++++-
3 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
index 956a3d97c42..e65114ff835 100644
--- a/boot/image-fdt.c
+++ b/boot/image-fdt.c
@@ -468,7 +468,8 @@ static int select_fdt(struct bootm_headers *images, const char *select, u8 arch,
fdt_noffset = boot_get_fdt_fit(images, fdt_addr,
&fit_uname_fdt,
&fit_uname_config,
- arch, &load, &len);
+ arch, &load, &len,
+ NULL);
if (fdt_noffset < 0)
return -ENOENT;
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 60e47db5fb0..4f4e7b4e06a 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -2242,10 +2242,13 @@ static inline int fit_image_load_storage(struct bootm_headers *images,
}
#endif
-int fit_image_load(struct bootm_headers *images, ulong addr,
- const char **fit_unamep, const char **fit_uname_configp,
- int arch, int ph_type, int bootstage_id,
- enum fit_load_op load_op, ulong *datap, ulong *lenp)
+/* Track load-time copies without changing the public fit_image_load() API. */
+static int fit_image_load_alloc(struct bootm_headers *images, ulong addr,
+ const char **fit_unamep,
+ const char **fit_uname_configp,
+ int arch, int ph_type, int bootstage_id,
+ enum fit_load_op load_op, ulong *datap,
+ ulong *lenp, bool *ownedp)
{
int image_type = image_ph_type(ph_type);
int cfg_noffset, noffset;
@@ -2255,6 +2258,7 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
const void *fit;
void *buf;
void *loadbuf;
+ bool load_owned = false;
size_t size;
int type_ok, os_ok;
ulong load, load_end, data, len;
@@ -2262,6 +2266,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
const char *prop_name;
int ret;
+ if (ownedp)
+ *ownedp = false;
fit = map_sysmem(addr, 0);
fit_uname = fit_unamep ? *fit_unamep : NULL;
fit_uname_config = fit_uname_configp ? *fit_uname_configp : NULL;
@@ -2487,6 +2493,9 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
log_debug("decompressing image\n");
if (load == data) {
loadbuf = aligned_alloc(8, max_decomp_len);
+ if (!loadbuf)
+ return -ENOMEM;
+ load_owned = true;
load = map_to_sysmem(loadbuf);
} else {
loadbuf = map_sysmem(load, max_decomp_len);
@@ -2494,13 +2503,17 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
if (image_decomp(comp, load, data, image_type,
loadbuf, buf, len, max_decomp_len, &load_end)) {
printf("Error decompressing %s\n", prop_name);
-
+ if (load_owned)
+ free(loadbuf);
return -ENOEXEC;
}
len = load_end - load;
} else if (load_op != FIT_LOAD_IGNORED && image_type == IH_TYPE_FLATDT &&
(load & 7)) {
loadbuf = aligned_alloc(8, len);
+ if (!loadbuf)
+ return -ENOMEM;
+ load_owned = true;
load = map_to_sysmem(loadbuf);
memcpy(loadbuf, buf, len);
} else if (load != data) {
@@ -2517,6 +2530,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
if (load_op != FIT_LOAD_IGNORED && image_type == IH_TYPE_FLATDT &&
fdt_check_header(loadbuf)) {
puts("Subimage data is not a FDT\n");
+ if (load_owned)
+ free(loadbuf);
return -ENOEXEC;
}
@@ -2526,6 +2541,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
*datap = load;
*lenp = len;
+ if (ownedp)
+ *ownedp = load_owned;
if (fit_unamep)
*fit_unamep = (char *)fit_uname;
if (fit_uname_configp)
@@ -2535,6 +2552,16 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
return noffset;
}
+int fit_image_load(struct bootm_headers *images, ulong addr,
+ const char **fit_unamep, const char **fit_uname_configp,
+ int arch, int ph_type, int bootstage_id,
+ enum fit_load_op load_op, ulong *datap, ulong *lenp)
+{
+ return fit_image_load_alloc(images, addr, fit_unamep, fit_uname_configp,
+ arch, ph_type, bootstage_id, load_op, datap,
+ lenp, NULL);
+}
+
int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
ulong *setup_start, ulong *setup_len)
{
@@ -2631,7 +2658,7 @@ out:
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp)
+ int arch, ulong *datap, ulong *lenp, bool *ownedp)
{
int fdt_noffset, cfg_noffset, count;
const void *fit;
@@ -2639,7 +2666,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char *fit_uname_config = NULL;
char *fit_uname_config_copy = NULL;
char *next_config = NULL;
- ulong load, len;
+ ulong load = 0, len = 0;
+ bool load_owned = false;
#ifdef CONFIG_OF_LIBFDT_OVERLAY
ulong ovload, ovlen, ovcopylen, need;
const char *uconfig;
@@ -2651,6 +2679,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
#endif
fit_uname = fit_unamep ? *fit_unamep : NULL;
+ if (ownedp)
+ *ownedp = false;
if (fit_uname_configp && *fit_uname_configp) {
fit_uname_config_copy = strdup(*fit_uname_configp);
@@ -2664,11 +2694,11 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
fit_uname_config = fit_uname_config_copy;
}
- fdt_noffset = fit_image_load(images,
- addr, &fit_uname, &fit_uname_config,
- arch, IH_TYPE_FLATDT,
- BOOTSTAGE_ID_FIT_FDT_START,
- FIT_LOAD_OPTIONAL, &load, &len);
+ fdt_noffset = fit_image_load_alloc(images, addr, &fit_uname,
+ &fit_uname_config, arch, IH_TYPE_FLATDT,
+ BOOTSTAGE_ID_FIT_FDT_START,
+ FIT_LOAD_OPTIONAL, &load, &len,
+ &load_owned);
if (fdt_noffset < 0)
goto out;
@@ -2699,6 +2729,10 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
err = boot_get_fdt_fit_into_buffer(map_sysmem(load, len), len,
CONFIG_SYS_FDT_PAD, 0, &base_buf,
&base_buf_size);
+ if (load_owned) {
+ free(map_sysmem(load, len));
+ load_owned = false;
+ }
if (err < 0) {
if (err != -ENOMEM)
printf("Required FDT copy for applying DTOs failed: %s\n",
@@ -2809,9 +2843,17 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
out:
#ifdef CONFIG_OF_LIBFDT_OVERLAY
- if (fdt_noffset >= 0 && base_buf)
+ if (fdt_noffset >= 0 && base_buf) {
load = map_to_sysmem(base_buf);
+ load_owned = true;
+ }
#endif
+ if (fdt_noffset < 0 && load_owned) {
+ free(map_sysmem(load, len));
+ load_owned = false;
+ }
+ if (ownedp)
+ *ownedp = load_owned;
if (datap)
*datap = load;
if (lenp)
diff --git a/include/image.h b/include/image.h
index d991b4c2bc3..4ef5344c718 100644
--- a/include/image.h
+++ b/include/image.h
@@ -721,12 +721,15 @@ int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
* @param arch Expected architecture (IH_ARCH_...)
* @param datap Returns address of loaded image
* @param lenp Returns length of loaded image
+ * @param ownedp Returns true if the loaded image is separately allocated
+ * and must be freed by the caller; may be NULL if the caller
+ * does not need this information
*
* Return: node offset of base image, or -ve error code on error
*/
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp);
+ int arch, ulong *datap, ulong *lenp, bool *ownedp);
/**
* fit_image_load() - load an image from a FIT
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 1/5] boot: fit: tell callers when a devicetree buffer must be freed
2026-10-26 12:18 ` [PATCH v3 1/5] boot: fit: tell callers when a devicetree buffer must be freed Carlo Caione
@ 2026-11-09 17:27 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-11-09 17:27 UTC (permalink / raw)
To: Carlo Caione, u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, Ahmad Fatoum, Julien Masson, Vitor Sato Eschholz,
Randolph Sapp, Anton Ivanov, Daniel Golle, Aristo Chen,
Jonas Karlman, Jerome Forissier, Johan Jonker, James Hilliard,
Alexey Charkov, Vincent Jardin, Peter Robinson
On 10/26/26 7:18 AM, Carlo Caione wrote:
> The FIT devicetree loader can return either data inside the FIT or a
> newly allocated buffer. Callers which only need a temporary result
> cannot tell whether it is safe to free.
>
> Report when the returned buffer must be freed. This lets the new
> fdt fit command copy the result to its chosen address without leaking
> memory or freeing part of the original FIT. Also release temporary
> allocations when they are no longer needed, including on error.
>
> Reviewed-by: Simon Glass <sjg@chromium.org>
> Signed-off-by: Carlo Caione <ccaione@baylibre.com>
> ---
> boot/image-fdt.c | 3 ++-
> boot/image-fit.c | 68 +++++++++++++++++++++++++++++++++++++++++++++-----------
> include/image.h | 5 ++++-
> 3 files changed, 61 insertions(+), 15 deletions(-)
>
> diff --git a/boot/image-fdt.c b/boot/image-fdt.c
> index 956a3d97c42..e65114ff835 100644
> --- a/boot/image-fdt.c
> +++ b/boot/image-fdt.c
> @@ -468,7 +468,8 @@ static int select_fdt(struct bootm_headers *images, const char *select, u8 arch,
> fdt_noffset = boot_get_fdt_fit(images, fdt_addr,
> &fit_uname_fdt,
> &fit_uname_config,
> - arch, &load, &len);
> + arch, &load, &len,
> + NULL);
>
> if (fdt_noffset < 0)
> return -ENOENT;
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> index 60e47db5fb0..4f4e7b4e06a 100644
> --- a/boot/image-fit.c
> +++ b/boot/image-fit.c
> @@ -2242,10 +2242,13 @@ static inline int fit_image_load_storage(struct bootm_headers *images,
> }
> #endif
>
> -int fit_image_load(struct bootm_headers *images, ulong addr,
> - const char **fit_unamep, const char **fit_uname_configp,
> - int arch, int ph_type, int bootstage_id,
> - enum fit_load_op load_op, ulong *datap, ulong *lenp)
> +/* Track load-time copies without changing the public fit_image_load() API. */
> +static int fit_image_load_alloc(struct bootm_headers *images, ulong addr,
> + const char **fit_unamep,
> + const char **fit_uname_configp,
> + int arch, int ph_type, int bootstage_id,
> + enum fit_load_op load_op, ulong *datap,
> + ulong *lenp, bool *ownedp)
> {
> int image_type = image_ph_type(ph_type);
> int cfg_noffset, noffset;
> @@ -2255,6 +2258,7 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> const void *fit;
> void *buf;
> void *loadbuf;
> + bool load_owned = false;
> size_t size;
> int type_ok, os_ok;
> ulong load, load_end, data, len;
> @@ -2262,6 +2266,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> const char *prop_name;
> int ret;
>
> + if (ownedp)
> + *ownedp = false;
> fit = map_sysmem(addr, 0);
> fit_uname = fit_unamep ? *fit_unamep : NULL;
> fit_uname_config = fit_uname_configp ? *fit_uname_configp : NULL;
> @@ -2487,6 +2493,9 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> log_debug("decompressing image\n");
> if (load == data) {
> loadbuf = aligned_alloc(8, max_decomp_len);
> + if (!loadbuf)
> + return -ENOMEM;
Checking for failed allocation seems like separate change that should get
it's own patch.
and Fixes: 8fbcc0e0e839 ("boot: Assure FDT is always at 8-byte aligned address")
> + load_owned = true;
I would rename this to loadbuf_owned since it is the loadbuf pointer that
is being freed later.
> load = map_to_sysmem(loadbuf);
> } else {
> loadbuf = map_sysmem(load, max_decomp_len);
> @@ -2494,13 +2503,17 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> if (image_decomp(comp, load, data, image_type,
> loadbuf, buf, len, max_decomp_len, &load_end)) {
> printf("Error decompressing %s\n", prop_name);
> -
> + if (load_owned)
> + free(loadbuf);
Freeing on error also technically fixes a leak in the existing code, so
could be considered for a separate patch as well.
> return -ENOEXEC;
> }
> len = load_end - load;
> } else if (load_op != FIT_LOAD_IGNORED && image_type == IH_TYPE_FLATDT &&
> (load & 7)) {
> loadbuf = aligned_alloc(8, len);
> + if (!loadbuf)
> + return -ENOMEM;
> + load_owned = true;
> load = map_to_sysmem(loadbuf);
> memcpy(loadbuf, buf, len);
> } else if (load != data) {
> @@ -2517,6 +2530,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> if (load_op != FIT_LOAD_IGNORED && image_type == IH_TYPE_FLATDT &&
> fdt_check_header(loadbuf)) {
> puts("Subimage data is not a FDT\n");
> + if (load_owned)
> + free(loadbuf);
> return -ENOEXEC;
> }
>
> @@ -2526,6 +2541,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
>
> *datap = load;
> *lenp = len;
> + if (ownedp)
> + *ownedp = load_owned;
> if (fit_unamep)
> *fit_unamep = (char *)fit_uname;
> if (fit_uname_configp)
> @@ -2535,6 +2552,16 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> return noffset;
> }
>
> +int fit_image_load(struct bootm_headers *images, ulong addr,
> + const char **fit_unamep, const char **fit_uname_configp,
> + int arch, int ph_type, int bootstage_id,
> + enum fit_load_op load_op, ulong *datap, ulong *lenp)
> +{
> + return fit_image_load_alloc(images, addr, fit_unamep, fit_uname_configp,
> + arch, ph_type, bootstage_id, load_op, datap,
> + lenp, NULL);
> +}
> +
> int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
> ulong *setup_start, ulong *setup_len)
> {
> @@ -2631,7 +2658,7 @@ out:
>
> int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> const char **fit_unamep, const char **fit_uname_configp,
> - int arch, ulong *datap, ulong *lenp)
> + int arch, ulong *datap, ulong *lenp, bool *ownedp)
> {
> int fdt_noffset, cfg_noffset, count;
> const void *fit;
> @@ -2639,7 +2666,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> const char *fit_uname_config = NULL;
> char *fit_uname_config_copy = NULL;
> char *next_config = NULL;
> - ulong load, len;
> + ulong load = 0, len = 0;
> + bool load_owned = false;
> #ifdef CONFIG_OF_LIBFDT_OVERLAY
> ulong ovload, ovlen, ovcopylen, need;
> const char *uconfig;
> @@ -2651,6 +2679,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> #endif
>
> fit_uname = fit_unamep ? *fit_unamep : NULL;
> + if (ownedp)
> + *ownedp = false;
>
> if (fit_uname_configp && *fit_uname_configp) {
> fit_uname_config_copy = strdup(*fit_uname_configp);
> @@ -2664,11 +2694,11 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> fit_uname_config = fit_uname_config_copy;
> }
>
> - fdt_noffset = fit_image_load(images,
> - addr, &fit_uname, &fit_uname_config,
> - arch, IH_TYPE_FLATDT,
> - BOOTSTAGE_ID_FIT_FDT_START,
> - FIT_LOAD_OPTIONAL, &load, &len);
> + fdt_noffset = fit_image_load_alloc(images, addr, &fit_uname,
> + &fit_uname_config, arch, IH_TYPE_FLATDT,
> + BOOTSTAGE_ID_FIT_FDT_START,
> + FIT_LOAD_OPTIONAL, &load, &len,
> + &load_owned);
>
> if (fdt_noffset < 0)
> goto out;
> @@ -2699,6 +2729,10 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> err = boot_get_fdt_fit_into_buffer(map_sysmem(load, len), len,
> CONFIG_SYS_FDT_PAD, 0, &base_buf,
> &base_buf_size);
> + if (load_owned) {
> + free(map_sysmem(load, len));
> + load_owned = false;
> + }
> if (err < 0) {
> if (err != -ENOMEM)
> printf("Required FDT copy for applying DTOs failed: %s\n",
> @@ -2809,9 +2843,17 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
>
> out:
> #ifdef CONFIG_OF_LIBFDT_OVERLAY
> - if (fdt_noffset >= 0 && base_buf)
> + if (fdt_noffset >= 0 && base_buf) {
> load = map_to_sysmem(base_buf);
> + load_owned = true;
> + }
> #endif
> + if (fdt_noffset < 0 && load_owned) {
> + free(map_sysmem(load, len));
> + load_owned = false;
> + }
> + if (ownedp)
> + *ownedp = load_owned;
> if (datap)
> *datap = load;
> if (lenp)
> diff --git a/include/image.h b/include/image.h
> index d991b4c2bc3..4ef5344c718 100644
> --- a/include/image.h
> +++ b/include/image.h
> @@ -721,12 +721,15 @@ int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
> * @param arch Expected architecture (IH_ARCH_...)
> * @param datap Returns address of loaded image
> * @param lenp Returns length of loaded image
> + * @param ownedp Returns true if the loaded image is separately allocated
> + * and must be freed by the caller; may be NULL if the caller
> + * does not need this information
It's a bit long, but caller_must_free_datap would be a more clear name.
Also, if I understand correctly, caller would need to call:
free(map_sysmem(load, len))
Which isn't obvious at all, so should be documented.
> *
> * Return: node offset of base image, or -ve error code on error
> */
> int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
> const char **fit_unamep, const char **fit_uname_configp,
> - int arch, ulong *datap, ulong *lenp);
> + int arch, ulong *datap, ulong *lenp, bool *ownedp);
>
> /**
> * fit_image_load() - load an image from a FIT
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 2/5] boot: fit: reject configurations with missing or invalid overlays
2026-10-26 12:18 [PATCH v3 0/5] Prepare devicetrees from FIT configurations with fdt fit Carlo Caione
2026-10-26 12:18 ` [PATCH v3 1/5] boot: fit: tell callers when a devicetree buffer must be freed Carlo Caione
@ 2026-10-26 12:18 ` Carlo Caione
2026-10-26 12:18 ` [PATCH v3 3/5] boot: fit: let callers choose where to place the devicetree Carlo Caione
` (2 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2026-10-26 12:18 UTC (permalink / raw)
To: u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, David Lechner, Ahmad Fatoum, Julien Masson,
Vitor Sato Eschholz, Randolph Sapp, Anton Ivanov, Daniel Golle,
Aristo Chen, Jonas Karlman, Jerome Forissier, Johan Jonker,
James Hilliard, Alexey Charkov, Vincent Jardin, Peter Robinson,
Carlo Caione
An overlay listed in a FIT configuration is part of the requested
devicetree, not an optional addition. Currently, a failed load or hash
check can be ignored and boot continues with that overlay missing.
For signed configurations, this also means using a combination that
was not signed.
Reject the devicetree if any listed overlay cannot be loaded or
verified. A missing image must be treated as an error too, rather
than falling back to the default configuration's base devicetree.
This also affects bootm, even when fdt fit is disabled. Boards which
previously booted despite a broken overlay will now stop at the error.
Add a bootm regression test covering a valid overlay, a corrupted
overlay hash and a missing overlay image.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
boot/image-fit.c | 12 +++++-----
test/py/tests/test_fit.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 4f4e7b4e06a..83e731402d6 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -2675,7 +2675,7 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
void *ovcopy = NULL;
void *base_buf = NULL;
ulong base_buf_size = 0;
- int i, err, noffset, ov_noffset;
+ int i, err, ov_noffset;
#endif
fit_uname = fit_unamep ? *fit_unamep : NULL;
@@ -2752,9 +2752,8 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
/* apply extra configs in FIT first, followed by args */
for (i = 1; ; i++) {
if (i < count) {
- noffset = fit_conf_get_prop_node_index(fit, cfg_noffset,
- FIT_FDT_PROP, i);
- uname = fit_get_name(fit, noffset, NULL);
+ uname = fdt_stringlist_get(fit, cfg_noffset,
+ FIT_FDT_PROP, i, NULL);
uconfig = NULL;
} else {
if (!next_config)
@@ -2786,8 +2785,9 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
BOOTSTAGE_ID_FIT_FDT_START,
FIT_LOAD_IGNORED, &ovload, &ovlen);
if (ov_noffset < 0) {
- printf("load of %s failed\n", uname);
- continue;
+ printf("load of %s failed\n", uname ? uname : uconfig);
+ fdt_noffset = ov_noffset;
+ goto out;
}
debug("%s loaded at 0x%08lx len=0x%08lx\n",
uname, ovload, ovlen);
diff --git a/test/py/tests/test_fit.py b/test/py/tests/test_fit.py
index 0edf875a9e1..3113209d911 100755
--- a/test/py/tests/test_fit.py
+++ b/test/py/tests/test_fit.py
@@ -397,6 +397,66 @@ class TestFitImage:
self.check_not_equal(params, 'ramdisk', 'ramdisk_out',
'Ramdisk loaded but should not be')
+ @pytest.mark.buildconfigspec('cmd_bootm')
+ @pytest.mark.buildconfigspec('cmd_fdt')
+ @pytest.mark.buildconfigspec('of_libfdt_overlay')
+ @pytest.mark.requiredtool('fdtput')
+ @pytest.mark.parametrize('overlay_state', ['valid', 'bad-hash', 'missing'])
+ def test_fit_overlay_load(self, ubman, fsetup, overlay_state):
+ """bootm must not skip a listed overlay that fails to load or verify"""
+ overlay_dts = '''
+/dts-v1/;
+/plugin/;
+
+&{/} {
+ test-overlay = "applied";
+};
+'''
+ overlay_image = '''
+ overlay-1 {
+ data = /incbin/("%(overlay)s");
+ type = "flat_dt";
+ arch = "sandbox";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ };
+'''
+ params = dict(fsetup, overlay=fit_util.make_dtb(
+ ubman, overlay_dts, 'bootm-overlay'))
+ its = BASE_ITS.replace(' images {',
+ ' images {' + overlay_image)
+ its = its.replace('fdt = "fdt-1";', 'fdt = "fdt-1", "overlay-1";')
+ fit = fit_util.make_fit(ubman, params['mkimage'], its, params,
+ basename=f'bootm-overlay-{overlay_state}.fit')
+ if overlay_state == 'bad-hash':
+ utils.run_and_log(ubman, [
+ 'fdtput', '-t', 'x', fit, '/images/overlay-1/hash-1',
+ 'value', *(['0'] * 8)])
+ elif overlay_state == 'missing':
+ utils.run_and_log(ubman, ['fdtput', '-r', fit, '/images/overlay-1'])
+
+ try:
+ ubman.run_command('setenv verify yes')
+ output = ubman.run_command(
+ f'host load hostfs 0 {params["fit_addr"]:x} {fit}')
+ assert 'bytes read' in output
+ output = ubman.run_command(
+ f'bootm start {params["fit_addr"]:x}#conf-1; echo rc=$?')
+ if overlay_state == 'valid':
+ assert output.rstrip().endswith('rc=0'), output
+ assert 'test-overlay = "applied"' in ubman.run_command(
+ 'fdt print / test-overlay')
+ else:
+ assert output.rstrip().endswith('rc=1'), output
+ assert 'load of overlay-1 failed' in output
+ assert 'Booting using the fdt blob' not in output
+ if overlay_state == 'bad-hash':
+ assert 'Bad hash value' in output
+ finally:
+ ubman.restart_uboot()
+
def test_fit_kernel_fdt_ramdisk_load(self, ubman, fsetup):
"""Test loading a FIT image with kernel, FDT, and ramdisk"""
cmds, params, _ = self.prepare(
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 3/5] boot: fit: let callers choose where to place the devicetree
2026-10-26 12:18 [PATCH v3 0/5] Prepare devicetrees from FIT configurations with fdt fit Carlo Caione
2026-10-26 12:18 ` [PATCH v3 1/5] boot: fit: tell callers when a devicetree buffer must be freed Carlo Caione
2026-10-26 12:18 ` [PATCH v3 2/5] boot: fit: reject configurations with missing or invalid overlays Carlo Caione
@ 2026-10-26 12:18 ` Carlo Caione
2026-10-26 12:18 ` [PATCH v3 4/5] cmd: fdt: add 'fdt fit' to prepare a devicetree for other boot commands Carlo Caione
2026-10-26 12:18 ` [PATCH v3 5/5] test: py: cover fdt fit preparation and failure handling Carlo Caione
4 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2026-10-26 12:18 UTC (permalink / raw)
To: u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, David Lechner, Ahmad Fatoum, Julien Masson,
Vitor Sato Eschholz, Randolph Sapp, Anton Ivanov, Daniel Golle,
Aristo Chen, Jonas Karlman, Jerome Forissier, Johan Jonker,
James Hilliard, Alexey Charkov, Vincent Jardin, Peter Robinson,
Carlo Caione
If a FIT specifies a load address, the loader writes the base
devicetree there before processing overlays. A caller which wants
to prepare the tree at an address of its own cannot prevent this
early write, even if an overlay later fails.
Allow callers to prepare the devicetree without writing to addresses
specified by the FIT. Alignment and decompression still take place,
so the result is ready to use. Keep the existing bootm behavior.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
boot/image-fdt.c | 3 ++-
boot/image-fit.c | 8 +++++---
include/image.h | 8 +++++++-
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
index e65114ff835..9678bf9c9b6 100644
--- a/boot/image-fdt.c
+++ b/boot/image-fdt.c
@@ -468,7 +468,8 @@ static int select_fdt(struct bootm_headers *images, const char *select, u8 arch,
fdt_noffset = boot_get_fdt_fit(images, fdt_addr,
&fit_uname_fdt,
&fit_uname_config,
- arch, &load, &len,
+ arch, FIT_LOAD_OPTIONAL,
+ &load, &len,
NULL);
if (fdt_noffset < 0)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 83e731402d6..c3d7b59bd06 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -2221,6 +2221,7 @@ static int fit_image_load_storage(struct bootm_headers *images, const void *fit,
fit_image_get_comp(fit, noffset, &img_comp);
if (img_comp == IH_COMP_NONE && load_op != FIT_LOAD_IGNORED &&
+ load_op != FIT_LOAD_PREPARE &&
!fit_image_get_load(fit, noffset, &img_load)) {
void *dst = map_sysmem(img_load, data_sz);
@@ -2445,7 +2446,7 @@ static int fit_image_load_alloc(struct bootm_headers *images, ulong addr,
data = map_to_sysmem(buf);
load = data;
- if (load_op == FIT_LOAD_IGNORED) {
+ if (load_op == FIT_LOAD_IGNORED || load_op == FIT_LOAD_PREPARE) {
log_debug("load_op: not loading\n");
/* Don't load */
} else if (fit_image_get_load(fit, noffset, &load)) {
@@ -2658,7 +2659,8 @@ out:
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp, bool *ownedp)
+ int arch, enum fit_load_op load_op, ulong *datap,
+ ulong *lenp, bool *ownedp)
{
int fdt_noffset, cfg_noffset, count;
const void *fit;
@@ -2697,7 +2699,7 @@ int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
fdt_noffset = fit_image_load_alloc(images, addr, &fit_uname,
&fit_uname_config, arch, IH_TYPE_FLATDT,
BOOTSTAGE_ID_FIT_FDT_START,
- FIT_LOAD_OPTIONAL, &load, &len,
+ load_op, &load, &len,
&load_owned);
if (fdt_noffset < 0)
diff --git a/include/image.h b/include/image.h
index 4ef5344c718..882a772a727 100644
--- a/include/image.h
+++ b/include/image.h
@@ -608,6 +608,7 @@ enum fit_load_op {
FIT_LOAD_OPTIONAL, /* Can be provided, but optional */
FIT_LOAD_OPTIONAL_NON_ZERO, /* Optional, a value of 0 is ignored */
FIT_LOAD_REQUIRED, /* Must be provided */
+ FIT_LOAD_PREPARE, /* Ignore load address, but align/decompress */
};
int boot_get_setup(struct bootm_headers *images, uint8_t arch, ulong *setup_start,
@@ -719,6 +720,10 @@ int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
* name (e.g. "conf-1") or NULL to use the default. On
* exit points to the selected configuration name.
* @param arch Expected architecture (IH_ARCH_...)
+ * @param load_op Load-address policy for the base image only; see
+ * enum fit_load_op. FIT_LOAD_PREPARE ignores the address but
+ * still aligns/decompresses as needed. Overlays always use
+ * FIT_LOAD_IGNORED.
* @param datap Returns address of loaded image
* @param lenp Returns length of loaded image
* @param ownedp Returns true if the loaded image is separately allocated
@@ -729,7 +734,8 @@ int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
*/
int boot_get_fdt_fit(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
- int arch, ulong *datap, ulong *lenp, bool *ownedp);
+ int arch, enum fit_load_op load_op, ulong *datap,
+ ulong *lenp, bool *ownedp);
/**
* fit_image_load() - load an image from a FIT
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 4/5] cmd: fdt: add 'fdt fit' to prepare a devicetree for other boot commands
2026-10-26 12:18 [PATCH v3 0/5] Prepare devicetrees from FIT configurations with fdt fit Carlo Caione
` (2 preceding siblings ...)
2026-10-26 12:18 ` [PATCH v3 3/5] boot: fit: let callers choose where to place the devicetree Carlo Caione
@ 2026-10-26 12:18 ` Carlo Caione
2026-11-09 17:27 ` David Lechner
2026-10-26 12:18 ` [PATCH v3 5/5] test: py: cover fdt fit preparation and failure handling Carlo Caione
4 siblings, 1 reply; 9+ messages in thread
From: Carlo Caione @ 2026-10-26 12:18 UTC (permalink / raw)
To: u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, David Lechner, Ahmad Fatoum, Julien Masson,
Vitor Sato Eschholz, Randolph Sapp, Anton Ivanov, Daniel Golle,
Aristo Chen, Jonas Karlman, Jerome Forissier, Johan Jonker,
James Hilliard, Alexey Charkov, Vincent Jardin, Peter Robinson,
Carlo Caione
A board may keep its devicetree and overlays in a FIT while booting
a kernel or EFI application from elsewhere. Boot scripts need a way
to verify and assemble that devicetree without asking bootm to boot
the FIT.
Add fdt fit to prepare the devicetree described by one FIT
configuration and copy it to an address chosen by the script. The
result can then be passed to bootefi, booti or another boot command.
Always check image hashes and enforce any required configuration
signatures, even when verify=no. Reject chained configurations:
signing each configuration separately does not authenticate the
combination.
Only copy the completed result after verification, overlay
application and destination checks succeed. The script remains
responsible for loading the FIT and stopping the boot on failure.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
cmd/Kconfig | 9 ++++++
cmd/fdt.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++
doc/usage/cmd/fdt.rst | 75 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 167 insertions(+)
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 7124b961dc2..c81534810c5 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -577,6 +577,15 @@ config CMD_FDT
help
Do FDT related setup before booting into the Operating System.
+config CMD_FDT_FIT
+ bool "fdt fit - load the devicetree of a FIT configuration"
+ depends on CMD_FDT && FIT
+ imply OF_LIBFDT_OVERLAY
+ help
+ Add the 'fdt fit' subcommand, which loads the devicetree of a FIT
+ configuration, applies the overlays the configuration lists and
+ copies the result to a given address.
+
config CMD_EXTENSION
bool "Extension board management command"
select CMD_FDT
diff --git a/cmd/fdt.c b/cmd/fdt.c
index 7e2abf96103..94b2376d072 100644
--- a/cmd/fdt.c
+++ b/cmd/fdt.c
@@ -11,6 +11,7 @@
#include <env.h>
#include <fdt_support.h>
#include <image.h>
+#include <lmb.h>
#include <malloc.h>
#include <mapmem.h>
#include <asm/global_data.h>
@@ -145,6 +146,79 @@ static int fdt_get_header_value(int argc, char *const argv[])
return CMD_RET_FAILURE;
}
+/*
+ * fdt fit <addr>[#<conf>] <dest> [<length>]
+ *
+ * Load the devicetree of a FIT configuration, with its overlays applied, and
+ * copy it to <dest>. Images are always verified, so the configuration must
+ * also satisfy any required signature in the control devicetree. Unlike bootm,
+ * image load addresses are ignored and configurations cannot be chained.
+ */
+static int fdt_fit(int argc, char *const argv[])
+{
+ struct bootm_headers images = { .verify = 1 };
+ const char *conf = NULL;
+ ulong addr, dest, data, len;
+ bool owned;
+ void *fdt;
+ int ret;
+
+ if (argc < 2 || argc > 3)
+ return CMD_RET_USAGE;
+
+ if (!fit_parse_conf(argv[0], image_load_addr, &addr, &conf))
+ addr = hextoul(argv[0], NULL);
+ dest = hextoul(argv[1], NULL);
+
+ /* Only a single configuration authenticates the whole combination */
+ if (conf && strchr(conf, '#')) {
+ puts("Chained configurations are not supported\n");
+ return CMD_RET_FAILURE;
+ }
+
+ if (boot_get_fdt_fit(&images, addr, NULL, &conf, IH_ARCH_DEFAULT,
+ FIT_LOAD_PREPARE, &data, &len, &owned) < 0)
+ return CMD_RET_FAILURE;
+
+ fdt = map_sysmem(data, len);
+ ret = CMD_RET_FAILURE;
+ if (fdt_check_full(fdt, len)) {
+ puts("Invalid FDT in FIT\n");
+ goto out;
+ }
+
+ len = fdt_totalsize(fdt);
+ if (argc > 2) {
+ ulong newlen = hextoul(argv[2], NULL);
+
+ if (newlen < len) {
+ printf("New length 0x%lX < existing length 0x%lX, aborting.\n",
+ newlen, len);
+ goto out;
+ }
+ len = newlen;
+ }
+
+ if (CONFIG_IS_ENABLED(LMB) && lmb_read_check(dest, len)) {
+ puts("ERROR: trying to overwrite reserved memory...\n");
+ goto out;
+ }
+
+ ret = fdt_open_into(fdt, map_sysmem(dest, len), len);
+ if (ret) {
+ printf("libfdt fdt_open_into(): %s\n", fdt_strerror(ret));
+ ret = CMD_RET_FAILURE;
+ goto out;
+ }
+ ret = CMD_RET_SUCCESS;
+
+out:
+ if (owned)
+ free(fdt);
+
+ return ret;
+}
+
/*
* Flattened Device Tree command, see the help for parameter definitions.
*/
@@ -281,6 +355,10 @@ static int do_fdt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
return CMD_RET_SUCCESS;
}
+ /* Load the devicetree of a FIT configuration */
+ if (IS_ENABLED(CONFIG_CMD_FDT_FIT) && strncmp(argv[1], "fi", 2) == 0)
+ return fdt_fit(argc - 2, argv + 2);
+
if (!working_fdt) {
puts("No FDT memory address configured. Please configure\n"
"the FDT address via \"fdt addr <address>\" command.\n"
@@ -1124,6 +1202,11 @@ U_BOOT_LONGHELP(fdt,
#endif
#ifdef CONFIG_OF_SYSTEM_SETUP
"fdt systemsetup - Do system-specific set up\n"
+#endif
+#ifdef CONFIG_CMD_FDT_FIT
+ "fdt fit <addr>[#<conf>] <dest> [<length>] - Load the fdt of a FIT\n"
+ " configuration, apply its overlays and\n"
+ " copy it to <dest>\n"
#endif
"fdt move <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active\n"
"fdt resize [<extrasize>] - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed\n"
diff --git a/doc/usage/cmd/fdt.rst b/doc/usage/cmd/fdt.rst
index 493ed895bc7..88222a9939d 100644
--- a/doc/usage/cmd/fdt.rst
+++ b/doc/usage/cmd/fdt.rst
@@ -12,6 +12,7 @@ Synopsis
::
fdt addr [-cq] [addr [len]]
+ fdt fit <addr>[#<conf>] <dest> [<len>]
Description
-----------
@@ -48,6 +49,46 @@ If the `len` argument is provided, then the device tree is expanded to that
size. This can be used to make space for more nodes and properties. It is
assumed that there is enough space in memory for this expansion.
+fdt fit
+~~~~~~~
+
+This loads the device tree of a FIT configuration and copies it to `dest`. The
+FIT must already be in memory at `addr`.
+
+The configuration `conf` is selected as for the bootm command. If it is
+omitted, the configuration matching the control FDT is used when
+CONFIG_FIT_BEST_MATCH is enabled, otherwise the default configuration of the
+FIT. The `fdt` property of the configuration lists a base device tree followed
+by any overlays: the base device tree is loaded and the overlays are applied to
+it in order.
+
+Each image is checked against its hashes, whatever the value of the `verify`
+environment variable. With CONFIG_FIT_SIGNATURE, the configuration must also
+satisfy the signature policy of the control FDT, so an unsigned configuration
+is rejected when a key has `required = "conf"`. A configuration signature
+covers the hashes of all the images listed by the configuration, so it
+authenticates the base device tree, the overlays and their order as one unit.
+
+Unlike bootm, the command does not accept further configurations appended to
+`conf` with `#`. Each of them would be verified on its own, so a chain could
+combine device trees in a way which no configuration lists and nobody signed.
+List each valid combination of a base device tree and overlays in its own
+configuration instead.
+
+The command fails if any image cannot be loaded, verified or applied, and
+nothing is written to `dest` in that case. Unlike bootm, the command ignores
+FIT image `load` addresses: only the completed device tree is copied to
+`dest`, after checking that the destination does not overlap reserved memory
+when CONFIG_LMB is enabled. Alignment and decompression of the base device
+tree are still handled as needed.
+
+If the `len` argument is provided, then the device tree is expanded to that
+size, leaving space for later changes. The command fails if `len` is smaller
+than the device tree. Without `len`, the device tree is copied at its own size.
+The working FDT is not changed; use `fdt addr` to select the result.
+
+This subcommand requires CONFIG_CMD_FDT_FIT.
+
Example
-------
@@ -66,3 +107,37 @@ address and expand it to 0xf000 in size::
Working FDT set to 10000
=> md 10000 4
00010000: edfe0dd0 00f00000 78000000 7c270000 ...........x..'|
+
+Load a FIT holding a device tree and its overlays, then copy the device tree of
+the `conf-a` configuration, a base device tree with one overlay, to 2000000 and
+inspect it::
+
+ => load mmc 0#firmware 1000000 fdt.itb
+ 1771 bytes read in 1 ms (1.7 MiB/s)
+ => fdt fit 1000000#conf-a 2000000
+ ## Loading fdt (any) from FIT Image at 01000000 ...
+ Using 'conf-a' configuration
+ Verifying Hash Integrity ... OK
+ Trying 'base' fdt subimage
+ ...
+ Verifying Hash Integrity ... sha256+ OK
+ ## Loading fdt (any) from FIT Image at 01000000 ...
+ Trying 'ov-a' fdt subimage
+ ...
+ Verifying Hash Integrity ... sha256+ OK
+ => fdt addr 2000000
+ Working FDT set to 2000000
+ => fdt print /test-node
+ test-node {
+ ov-a-prop = "ov-a-value";
+ base-prop = "base-value";
+ order = "ov-a";
+ };
+
+A boot script can prepare a device tree kept with the firmware in this way and
+pass it to the boot command, stopping if any step fails::
+
+ if load mmc 0#firmware ${loadaddr} fdt.itb &&
+ fdt fit ${loadaddr}#conf-a ${fdt_addr_r}; then
+ bootefi bootmgr ${fdt_addr_r}
+ fi
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 4/5] cmd: fdt: add 'fdt fit' to prepare a devicetree for other boot commands
2026-10-26 12:18 ` [PATCH v3 4/5] cmd: fdt: add 'fdt fit' to prepare a devicetree for other boot commands Carlo Caione
@ 2026-11-09 17:27 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-11-09 17:27 UTC (permalink / raw)
To: Carlo Caione, u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, Ahmad Fatoum, Julien Masson, Vitor Sato Eschholz,
Randolph Sapp, Anton Ivanov, Daniel Golle, Aristo Chen,
Jonas Karlman, Jerome Forissier, Johan Jonker, James Hilliard,
Alexey Charkov, Vincent Jardin, Peter Robinson
On 10/26/26 7:18 AM, Carlo Caione wrote:
> A board may keep its devicetree and overlays in a FIT while booting
> a kernel or EFI application from elsewhere. Boot scripts need a way
> to verify and assemble that devicetree without asking bootm to boot
> the FIT.
>
...
> +/*
> + * fdt fit <addr>[#<conf>] <dest> [<length>]
> + *
> + * Load the devicetree of a FIT configuration, with its overlays applied, and
> + * copy it to <dest>. Images are always verified, so the configuration must
> + * also satisfy any required signature in the control devicetree. Unlike bootm,
> + * image load addresses are ignored and configurations cannot be chained.
> + */
> +static int fdt_fit(int argc, char *const argv[])
> +{
> + struct bootm_headers images = { .verify = 1 };
> + const char *conf = NULL;
> + ulong addr, dest, data, len;
> + bool owned;
> + void *fdt;
> + int ret;
> +
> + if (argc < 2 || argc > 3)
> + return CMD_RET_USAGE;
> +
> + if (!fit_parse_conf(argv[0], image_load_addr, &addr, &conf))
> + addr = hextoul(argv[0], NULL);
> + dest = hextoul(argv[1], NULL);
> +
> + /* Only a single configuration authenticates the whole combination */
> + if (conf && strchr(conf, '#')) {
> + puts("Chained configurations are not supported\n");
> + return CMD_RET_FAILURE;
> + }
> +
> + if (boot_get_fdt_fit(&images, addr, NULL, &conf, IH_ARCH_DEFAULT,
> + FIT_LOAD_PREPARE, &data, &len, &owned) < 0)
> + return CMD_RET_FAILURE;
> +
> + fdt = map_sysmem(data, len);
> + ret = CMD_RET_FAILURE;
> + if (fdt_check_full(fdt, len)) {
> + puts("Invalid FDT in FIT\n");
> + goto out;
> + }
> +
> + len = fdt_totalsize(fdt);
> + if (argc > 2) {
> + ulong newlen = hextoul(argv[2], NULL);
> +
> + if (newlen < len) {
> + printf("New length 0x%lX < existing length 0x%lX, aborting.\n",
> + newlen, len);
> + goto out;
> + }
> + len = newlen;
> + }
> +
> + if (CONFIG_IS_ENABLED(LMB) && lmb_read_check(dest, len)) {
> + puts("ERROR: trying to overwrite reserved memory...\n");
> + goto out;
> + }
> +
> + ret = fdt_open_into(fdt, map_sysmem(dest, len), len);
nit: its a bit awkward to use ret for both CMD_RET_ and other error type.
Would suggest to replace this one with e.g. err.
> + if (ret) {
> + printf("libfdt fdt_open_into(): %s\n", fdt_strerror(ret));
> + ret = CMD_RET_FAILURE;
Then we don't have to rember to restore the value that was already in ret
here.
> + goto out;
> + }
> + ret = CMD_RET_SUCCESS;
> +
> +out:
> + if (owned)
> + free(fdt);
> +
> + return ret;
> +}
> +
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 5/5] test: py: cover fdt fit preparation and failure handling
2026-10-26 12:18 [PATCH v3 0/5] Prepare devicetrees from FIT configurations with fdt fit Carlo Caione
` (3 preceding siblings ...)
2026-10-26 12:18 ` [PATCH v3 4/5] cmd: fdt: add 'fdt fit' to prepare a devicetree for other boot commands Carlo Caione
@ 2026-10-26 12:18 ` Carlo Caione
2026-11-09 17:28 ` David Lechner
4 siblings, 1 reply; 9+ messages in thread
From: Carlo Caione @ 2026-10-26 12:18 UTC (permalink / raw)
To: u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, David Lechner, Ahmad Fatoum, Julien Masson,
Vitor Sato Eschholz, Randolph Sapp, Anton Ivanov, Daniel Golle,
Aristo Chen, Jonas Karlman, Jerome Forissier, Johan Jonker,
James Hilliard, Alexey Charkov, Vincent Jardin, Peter Robinson,
Carlo Caione
Boot scripts rely on fdt fit either returning a complete, verified
devicetree or failing without touching the destination. Regressions
could otherwise leave a script with an incomplete tree or overwrite
memory before an error is reported.
Add sandbox coverage for successful preparation and failure cases,
including overlay order, compression and FIT load addresses. Check
that verify=no cannot bypass required signatures and that
individually signed configurations cannot be chained into a new
combination.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
configs/sandbox_defconfig | 1 +
test/py/tests/test_fdt_fit.py | 371 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 372 insertions(+)
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index f307972a762..cd44c42d954 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -65,6 +65,7 @@ CONFIG_CMD_BOOTZ=y
CONFIG_BOOTM_OPENRTOS=y
CONFIG_BOOTM_OSE=y
CONFIG_CMD_BOOTMENU=y
+CONFIG_CMD_FDT_FIT=y
CONFIG_CMD_ASKENV=y
CONFIG_CMD_GREPENV=y
CONFIG_CMD_ERASEENV=y
diff --git a/test/py/tests/test_fdt_fit.py b/test/py/tests/test_fdt_fit.py
new file mode 100644
index 00000000000..3a039b32515
--- /dev/null
+++ b/test/py/tests/test_fdt_fit.py
@@ -0,0 +1,371 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2026 BayLibre SAS
+
+"""Test the 'fdt fit' command
+
+Build a FIT holding a base devicetree and two overlays, with configurations
+which combine them, then check the devicetree produced for each
+configuration and that images failing to load or verify are rejected.
+"""
+
+import gzip
+import os
+import shutil
+
+import pytest
+import fit_util
+import utils
+
+FIT_ADDR = 0x1000000
+DEST_ADDR = 0x2000000
+
+BASE_DTS = '''
+/dts-v1/;
+
+/ {
+ model = "fdt fit test";
+
+ test-node {
+ base-prop = "base-value";
+ order = "base";
+ };
+};
+'''
+
+OVERLAY_DTS = '''
+/dts-v1/;
+/plugin/;
+
+&{/test-node} {
+ %(name)s-prop = "%(name)s-value";
+ order = "%(name)s";
+};
+'''
+
+FDT_IMAGE = '''
+ %(name)s {
+ data = /incbin/("%(fname)s");
+ type = "flat_dt";
+ arch = "sandbox";
+ compression = "%(compression)s";
+ hash-1 {
+ algo = "sha256";
+ };
+ };
+'''
+
+CONFIG = '''
+ %(name)s {
+ fdt = %(fdt)s;
+ %(sig)s
+ };
+'''
+
+SIGNATURE = '''signature {
+ algo = "sha256,rsa2048";
+ key-name-hint = "dev";
+ };'''
+
+def make_test_fit(ubman, basename, sig, compress_base=False):
+ """Create a FIT with a base devicetree, two overlays and configurations
+
+ Args:
+ ubman (ConsoleBase): U-Boot console
+ basename (str): Base name of the FIT to create
+ sig (str): Signature node to add to each configuration, or ''
+ compress_base (bool): Compress the base devicetree with gzip
+
+ Returns:
+ str: Filename of the FIT
+ """
+ images = {'base': fit_util.make_dtb(ubman, BASE_DTS, 'fdt-fit-base')}
+ if compress_base:
+ compressed = images['base'] + '.gz'
+ with open(images['base'], 'rb') as inf, gzip.open(compressed, 'wb') as outf:
+ shutil.copyfileobj(inf, outf)
+ images['base'] = compressed
+ for name in ('ov-a', 'ov-b'):
+ images[name] = fit_util.make_dtb(ubman, OVERLAY_DTS % {'name': name},
+ f'fdt-fit-{name}')
+
+ configs = {
+ 'conf-base': '"base"',
+ 'conf-a': '"base", "ov-a"',
+ 'conf-ab': '"base", "ov-a", "ov-b"',
+ 'conf-b': '"ov-b"',
+ }
+
+ its = '/dts-v1/;\n\n/ {\n\tdescription = "fdt fit test";\n'
+ its += '\t#address-cells = <1>;\n\n\timages {'
+ for name, fname in images.items():
+ compression = 'gzip' if name == 'base' and compress_base else 'none'
+ its += FDT_IMAGE % {'name': name, 'fname': fname,
+ 'compression': compression}
+ its += '\t};\n\n\tconfigurations {\n\t\tdefault = "conf-base";\n'
+ for name, fdt in configs.items():
+ its += CONFIG % {'name': name, 'fdt': fdt, 'sig': sig}
+ its += '\t};\n};\n'
+
+ its_fname = fit_util.make_its(ubman, its, {}, f'{basename}.its')
+ fit = fit_util.make_fname(ubman, f'{basename}.fit')
+ mkimage = os.path.join(ubman.config.build_dir, 'tools/mkimage')
+ utils.run_and_log(ubman, [mkimage, '-f', its_fname, fit])
+
+ return fit
+
+def corrupt_overlay(ubman, fit, basename):
+ """Copy a FIT, changing the data of the ov-b image but not its hash
+
+ Args:
+ ubman (ConsoleBase): U-Boot console
+ fit (str): Filename of the FIT to copy
+ basename (str): Base name of the copy
+
+ Returns:
+ str: Filename of the copy
+ """
+ with open(fit, 'rb') as inf:
+ data = inf.read()
+ assert data.count(b'ov-b-value') == 1
+ out = fit_util.make_fname(ubman, basename)
+ with open(out, 'wb') as outf:
+ outf.write(data.replace(b'ov-b-value', b'ov-b-evil!'))
+
+ return out
+
+def load_fit(ubman, fit):
+ """Load a file into memory at FIT_ADDR and clear the destination"""
+ output = ubman.run_command(f'host load hostfs - {FIT_ADDR:x} {fit}')
+ assert 'bytes read' in output
+ ubman.run_command(f'mw.l {DEST_ADDR:x} 0 400')
+
+def fdt_fit(ubman, args):
+ """Run 'fdt fit' and report whether it succeeded
+
+ Args:
+ ubman (ConsoleBase): U-Boot console
+ args (str): Arguments for the command
+
+ Returns:
+ tuple:
+ bool: True if the command succeeded
+ str: Command output
+ """
+ output = ubman.run_command(f'fdt fit {args}; echo rc=$?')
+ return output.rstrip().endswith('rc=0'), output
+
+def get_test_node(ubman):
+ """Return the test node of the devicetree at DEST_ADDR"""
+ ubman.run_command(f'fdt addr {DEST_ADDR:x}')
+ return ubman.run_command('fdt print /test-node')
+
+def check_dest_untouched(ubman, addr=DEST_ADDR):
+ """Check that nothing was written to a cleared destination"""
+ output = ubman.run_command(f'md.l {addr:x} 4')
+ assert '00000000 00000000 00000000 00000000' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fdt_fit')
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('fdtput')
+def test_fdt_fit(ubman):
+ """Test loading the devicetree of a FIT configuration"""
+ fit = make_test_fit(ubman, 'fdt-fit', '')
+
+ with ubman.log.section('Default configuration'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x} {DEST_ADDR:x}')
+ assert ok, output
+ assert "Using 'conf-base' configuration" in output
+ node = get_test_node(ubman)
+ assert 'base-prop = "base-value"' in node
+ assert 'ov-a-prop' not in node
+
+ with ubman.log.section('Overlays applied in order'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert ok, output
+ node = get_test_node(ubman)
+ assert 'base-prop = "base-value"' in node
+ assert 'ov-a-prop = "ov-a-value"' in node
+ assert 'ov-b-prop = "ov-b-value"' in node
+ assert 'order = "ov-b"' in node
+
+ with ubman.log.section('Chained configuration'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman,
+ f'{FIT_ADDR:x}#conf-a#conf-b {DEST_ADDR:x}')
+ assert not ok
+ assert 'Chained configurations are not supported' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Length'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-a {DEST_ADDR:x} 4000')
+ assert ok, output
+ ubman.run_command(f'fdt addr {DEST_ADDR:x}')
+ output = ubman.run_command('fdt header get size totalsize; echo $size')
+ assert output.splitlines()[-1] == '4000'
+
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-a {DEST_ADDR:x} 10')
+ assert not ok
+ assert 'aborting' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Missing configuration'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-none {DEST_ADDR:x}')
+ assert not ok
+ assert "Could not find configuration node 'conf-none'" in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Corrupted overlay'):
+ bad_fit = corrupt_overlay(ubman, fit, 'fdt-fit-bad-overlay.fit')
+ load_fit(ubman, bad_fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert not ok
+ assert 'Bad hash value' in output
+ assert 'load of ov-b failed' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Missing overlay'):
+ missing_fit = fit_util.make_fname(ubman, 'fdt-fit-missing.fit')
+ shutil.copyfile(fit, missing_fit)
+ utils.run_and_log(ubman, ['fdtput', '-t', 's', missing_fit,
+ '/configurations/conf-a', 'fdt', 'base',
+ 'ov-missing'])
+ load_fit(ubman, missing_fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-a {DEST_ADDR:x}')
+ assert not ok
+ assert 'load of ov-missing failed' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Not a FIT'):
+ load_fit(ubman, fit_util.make_fname(ubman, 'fdt-fit-base.dtb'))
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x} {DEST_ADDR:x}')
+ assert not ok
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Usage'):
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}')
+ assert not ok
+ assert 'Usage:' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fdt_fit')
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('fdtput')
+@pytest.mark.parametrize('compress_base', [False, True])
+def test_fdt_fit_load_addr(ubman, compress_base):
+ """Ignore FIT load addresses, including before an overlay failure"""
+ if compress_base and not ubman.config.buildconfig.get('config_gzip'):
+ pytest.skip('gzip support not enabled')
+
+ fit = make_test_fit(ubman, 'fdt-fit-load', '', compress_base)
+ utils.run_and_log(ubman, ['fdtput', '-t', 'x', fit, '/images/base',
+ 'load', f'{DEST_ADDR:x}'])
+
+ with ubman.log.section('Load address equals destination, bad overlay'):
+ bad_fit = corrupt_overlay(ubman, fit, 'fdt-fit-load-bad.fit')
+ load_fit(ubman, bad_fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert not ok
+ assert 'load of ov-b failed' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Load address equals destination, short length'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-base {DEST_ADDR:x} 10')
+ assert not ok
+ assert 'aborting' in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Only the requested destination is written'):
+ load_addr = 0x3000000
+ utils.run_and_log(ubman, ['fdtput', '-t', 'x', fit, '/images/base',
+ 'load', f'{load_addr:x}'])
+ for conf in ('conf-base', 'conf-ab'):
+ load_fit(ubman, fit)
+ ubman.run_command(f'mw.l {load_addr:x} 0 400')
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#{conf} {DEST_ADDR:x}')
+ assert ok, output
+ node = get_test_node(ubman)
+ assert 'base-prop = "base-value"' in node
+ order = 'base' if conf == 'conf-base' else 'ov-b'
+ assert f'order = "{order}"' in node
+ check_dest_untouched(ubman, load_addr)
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fdt_fit')
+@pytest.mark.buildconfigspec('fit_signature')
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('openssl')
+def test_fdt_fit_signed(ubman):
+ """Test 'fdt fit' with a required configuration signature
+
+ This restarts U-Boot with a control devicetree holding the public key,
+ so it must run on sandbox.
+ """
+ keydir = fit_util.make_fname(ubman, 'fdt-fit-keys')
+ os.makedirs(keydir, exist_ok=True)
+ key = os.path.join(keydir, 'dev')
+ utils.run_and_log(ubman, f'openssl genpkey -algorithm RSA -out {key}.key '
+ '-pkeyopt rsa_keygen_bits:2048 '
+ '-pkeyopt rsa_keygen_pubexp:65537')
+ utils.run_and_log(ubman, f'openssl req -batch -new -x509 -key {key}.key '
+ f'-out {key}.crt')
+
+ vboot_dts = os.path.join(ubman.config.source_dir,
+ 'test/py/tests/vboot/sandbox-u-boot.dts')
+ dtb = fit_util.make_fname(ubman, 'fdt-fit-u-boot.dtb')
+ utils.run_and_log(ubman, ['dtc', vboot_dts, '-O', 'dtb', '-o', dtb])
+
+ unsigned_fit = make_test_fit(ubman, 'fdt-fit-unsigned', '')
+ fit = make_test_fit(ubman, 'fdt-fit-signed', SIGNATURE)
+ mkimage = os.path.join(ubman.config.build_dir, 'tools/mkimage')
+ utils.run_and_log(ubman, [mkimage, '-F', '-k', keydir, '-K', dtb, '-r',
+ fit])
+ bad_fit = corrupt_overlay(ubman, fit, 'fdt-fit-signed-bad-overlay.fit')
+
+ old_dtb = ubman.config.dtb
+ try:
+ ubman.config.dtb = dtb
+ ubman.restart_uboot()
+ ubman.run_command('setenv verify no')
+
+ with ubman.log.section('Signed configuration'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert ok, output
+ assert 'sha256,rsa2048:dev+' in output
+ node = get_test_node(ubman)
+ assert 'order = "ov-b"' in node
+
+ with ubman.log.section('Chained signed configurations'):
+ # Each is signed, but no configuration lists base plus only ov-b
+ for first in ('conf-a', 'conf-base'):
+ load_fit(ubman, fit)
+ ok, output = fdt_fit(ubman,
+ f'{FIT_ADDR:x}#{first}#conf-b {DEST_ADDR:x}')
+ assert not ok
+ assert 'Chained configurations are not supported' in output
+ assert 'dev+' not in output
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Unsigned configuration'):
+ load_fit(ubman, unsigned_fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert not ok
+ check_dest_untouched(ubman)
+
+ with ubman.log.section('Signed configuration, corrupted overlay'):
+ load_fit(ubman, bad_fit)
+ ok, output = fdt_fit(ubman, f'{FIT_ADDR:x}#conf-ab {DEST_ADDR:x}')
+ assert not ok
+ assert 'sha256,rsa2048:dev+' in output
+ assert 'load of ov-b failed' in output
+ check_dest_untouched(ubman)
+ finally:
+ ubman.config.dtb = old_dtb
+ ubman.restart_uboot()
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 5/5] test: py: cover fdt fit preparation and failure handling
2026-10-26 12:18 ` [PATCH v3 5/5] test: py: cover fdt fit preparation and failure handling Carlo Caione
@ 2026-11-09 17:28 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-11-09 17:28 UTC (permalink / raw)
To: Carlo Caione, u-boot
Cc: Heinrich Schuchardt, Ilias Apalodimas, Simon Glass, Tom Rini,
E Shattow, Ahmad Fatoum, Julien Masson, Vitor Sato Eschholz,
Randolph Sapp, Anton Ivanov, Daniel Golle, Aristo Chen,
Jonas Karlman, Jerome Forissier, Johan Jonker, James Hilliard,
Alexey Charkov, Vincent Jardin, Peter Robinson
On 10/26/26 7:18 AM, Carlo Caione wrote:
> Boot scripts rely on fdt fit either returning a complete, verified
> devicetree or failing without touching the destination. Regressions
> could otherwise leave a script with an incomplete tree or overwrite
> memory before an error is reported.
>
...
> diff --git a/test/py/tests/test_fdt_fit.py b/test/py/tests/test_fdt_fit.py
> new file mode 100644
> index 00000000000..3a039b32515
> --- /dev/null
> +++ b/test/py/tests/test_fdt_fit.py
> @@ -0,0 +1,371 @@
> +# SPDX-License-Identifier: GPL-2.0+
nit: deprecated SPDX license identifier.
^ permalink raw reply [flat|nested] 9+ messages in thread