Merge patch series "binman: Fix preload signing with encrypted FIT"

Yan WANG <yan.wang@softathome.com> says:

This series improves the reliability and efficiency of binman preload
header generation and test it against an encrypted FIT image signed with
a preload header.

When a preload header references other entries (e.g. an encrypted FIT)
through the collection etype, the referenced entries may be rebuilt
multiple times during binman processing. This becomes problematic when
the referenced entry produces non-deterministic output, such as FIT
encryption using random IVs or timestamps, since rebuilding the entry
changes the data.

This series ensures that referenced entries are built only once and that
preload signing is performed after all data is collected. It also avoids
unnecessary repacking or repeated signing operations by the preload.

The changes include:
  * generate preload header placeholders in ObtainContents() and sign
    data only once in ProcessContentsUpdate()
  * mark referenced entries as build_done in the collection etype to
    avoid rebuilding data
  * add a functional test for signing an encrypted FIT with a preload
    header

Link: https://lore.kernel.org/r/20260417083050.499955-1-yan.wang@softathome.com
This commit is contained in:
Tom Rini
2026-05-05 10:37:16 -06:00
5 changed files with 97 additions and 10 deletions

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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";
};
};
};
};
};