From 5006121b44a2d147e7e426c74537b3566407e853 Mon Sep 17 00:00:00 2001 From: Paul HENRYS Date: Fri, 17 Apr 2026 10:30:48 +0200 Subject: binman: Generate preload header and sign data only once To optimize preload generation, generate the header and signatures only after all data has been collected in ProcessContentsUpdate(). This avoids signing the data multiple times. Since header_size is known upfront (from __init__), create a placeholder in `ObtainContents()` to avoid an extra packing pass when ProcessContentsUpdate() detects a size change. This reduces unnecessary repacking and signing operations. Signed-off-by: Paul HENRYS Reviewed-by: Simon Glass --- tools/binman/etype/pre_load.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/binman/etype/pre_load.py b/tools/binman/etype/pre_load.py index 00f1a896767..0d953cb258e 100644 --- a/tools/binman/etype/pre_load.py +++ b/tools/binman/etype/pre_load.py @@ -152,14 +152,11 @@ class Entry_pre_load(Entry_collection): return data + pad def ObtainContents(self): - """Obtain a placeholder for the header contents""" - # wait that the image is available - self.image = self.GetContents(False) - if self.image is None: - return False - self.SetContents(self._CreateHeader()) + """Create a placeholder for the header""" + self.SetContents(tools.get_bytes(0, self.header_size)) return True def ProcessContents(self): + self.image = self.GetContents(True) data = self._CreateHeader() return self.ProcessContentsUpdate(data) -- cgit v1.2.3 From 9ff82a771c44a8cf38bd52b556f685ddbc6f1a55 Mon Sep 17 00:00:00 2001 From: yan wang Date: Fri, 17 Apr 2026 10:30:49 +0200 Subject: binman: collection: Set build_done on referenced entries The collection etype uses phandles in the 'content' property to reference other entries. Mark each referenced entry with build_done to avoid rebuilding the same entry data multiple times. This is important for cases where rebuilding may change the data content, e.g. due to timestamps or random IVs in encryption. Refactor GetContentsByPhandle() to return both the entry object and its data. Signed-off-by: yan wang Reviewed-by: Simon Glass --- tools/binman/etype/collection.py | 9 +++++++-- tools/binman/etype/section.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/binman/etype/collection.py b/tools/binman/etype/collection.py index c532aafe3e7..866ea8dcc1e 100644 --- a/tools/binman/etype/collection.py +++ b/tools/binman/etype/collection.py @@ -45,12 +45,17 @@ class Entry_collection(Entry): self.Info('Getting contents, required=%s' % required) data = bytearray() for entry_phandle in self.content: - entry_data = self.section.GetContentsByPhandle(entry_phandle, self, - required) + entry, entry_data = self.section.GetContentsByPhandle( + entry_phandle, self, required) if not required and entry_data is None: self.Info('Contents not available yet') # Data not available yet return None + + # Mark referenced entries as build_done to avoid rebuilding + if required: + entry.mark_build_done() + data += entry_data self.Info('Returning contents size %x' % len(data)) diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 6a26d687056..8530b7ee17f 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -557,7 +557,8 @@ class Entry_section(Entry): return None Returns: - data from associated entry (as a string), or None if not found + tuple: (entry, data) where entry is the Entry object and data is + from that entry (as a string), or (entry, None) if data not found """ node = self._node.GetFdt().LookupPhandle(phandle) if not node: @@ -565,7 +566,7 @@ class Entry_section(Entry): entry = self.FindEntryByNode(node) if not entry: source_entry.Raise("Cannot find entry for node '%s'" % node.name) - return entry.GetData(required) + return entry, entry.GetData(required) def LookupEntry(self, entries, sym_name, msg): """Look up the entry for a binman symbol -- cgit v1.2.3 From b20d69e5f57bee299380bb30112856e51d8f26b0 Mon Sep 17 00:00:00 2001 From: Paul HENRYS Date: Fri, 17 Apr 2026 10:30:50 +0200 Subject: tools: binman: Test signing an encrypted FIT with a preload header Add a test to verify the preload header correctly signs an encrypted FIT. This test exercises the case where encryption uses random IVs that would change between mkimage calls. Signed-off-by: Paul HENRYS Reviewed-by: Simon Glass --- tools/binman/ftest.py | 21 ++++++++ .../test/security/pre_load_fit_encrypted.dts | 63 ++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tools/binman/test/security/pre_load_fit_encrypted.dts diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index ca5149ee654..da8325f820a 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -5895,6 +5895,27 @@ fdt fdtmap Extract the devicetree blob from the fdtmap data = self._DoReadFileDtb('security/pre_load_invalid_key.dts', entry_args=entry_args) + def testPreLoadEncryptedFit(self): + """Test an encrypted FIT image with a pre-load header""" + entry_args = { + 'pre-load-key-path': os.path.join(self._binman_dir, 'test'), + } + data = tools.read_file(self.TestFile("fit/aes256.bin")) + self._MakeInputFile("keys/aes256.bin", data) + + keys_subdir = os.path.join(self._indir, "keys") + data = self._DoReadFileDtb( + 'security/pre_load_fit_encrypted.dts', entry_args=entry_args, + extra_indirs=[keys_subdir])[0] + + image_fname = tools.get_output_filename('image.bin') + is_signed = self._CheckPreload(image_fname, self.TestFile("dev.key")) + + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) + self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)]) + self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)]) + self.assertEqual(is_signed, True) + def _CheckSafeUniqueNames(self, *images): """Check all entries of given images for unsafe unique names""" for image in images: diff --git a/tools/binman/test/security/pre_load_fit_encrypted.dts b/tools/binman/test/security/pre_load_fit_encrypted.dts new file mode 100644 index 00000000000..f5e9bf9426c --- /dev/null +++ b/tools/binman/test/security/pre_load_fit_encrypted.dts @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pre-load { + content = <&image>; + algo-name = "sha256,rsa2048"; + key-name = "dev.key"; + header-size = <4096>; + version = <0x11223344>; + }; + + image: fit { + fit,encrypt; + description = "Test a FIT with encrypted data and signed with a preload"; + #address-cells = <1>; + + images { + u-boot { + description = "U-Boot"; + type = "firmware"; + arch = "arm64"; + os = "U-Boot"; + compression = "none"; + load = <00000000>; + entry = <00000000>; + cipher { + algo = "aes256"; + key-name-hint = "aes256"; + }; + u-boot-nodtb { + }; + }; + fdt-1 { + description = "Flattened Device Tree blob"; + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + cipher { + algo = "aes256"; + key-name-hint = "aes256"; + }; + u-boot-dtb { + }; + }; + }; + + configurations { + default = "conf-1"; + conf-1 { + description = "Boot U-Boot with FDT blob"; + firmware = "u-boot"; + fdt = "fdt-1"; + }; + }; + }; + }; +}; -- cgit v1.2.3