[PATCH v2 0/7] efi: CapsuleUpdate: support for dynamic UUIDs

As more boards adopt support for the EFI CapsuleUpdate mechanism, there is a growing issue of being able to target updates to them properly. The current mechanism of hardcoding UUIDs for each board at compile time is unsustainable, and maintaining lists of GUIDs is similarly cumbersome.
In this series, I propose that we adopt v5 GUIDs, these are generated by using a well-known salt GUID as well as board specific information (like the model/revision), these are hashed together and the result is truncated to form a new UUID.
The well-known salt GUID can be specific to the architecture (SoC vendor), or OEM. It is defined in the board defconfig so that vendors can easily bring their own.
Specifically, the following fields are used to generate a GUID for a particular fw_image:
* namespace salt * board compatible (usually the first entry in the dt root compatible array). * fw_image name (the string identifying the specific image, especially relevant for board that can update multiple images).
== Usage ==
Boards can integrate dynamic UUID support as follows:
1. Adjust Kconfig to depend on EFI_CAPSULE_DYNAMIC_UUIDS if EFI_HAVE_CAPSULE_SUPPORT. 2. Skip setting the fw_images image_type_id property. 3. Generate a UUID and set CONFIG_EFI_CAPSULE_NAMESPACE_UUID in your defconfig.
== Limitations ==
* Changing GUIDs
The primary limitation with this approach is that if any of the source fields change, so will the GUID for the board. It is therefore pretty important to ensure that GUID changes are caught during development.
* Supporting multiple boards with a single image
This now requires having an entry with the GUID for every board which might lead to larger UpdateCapsule images.
== Tooling ==
This series introduces a new tool: genguid. This can be used to generate the same GUIDs that the board would at runtime.
This series follows a related discussion started by Ilias: https://lore.kernel.org/u-boot/CAC_iWjJNHa4gMF897MqYZNdbgjFG8K4kwGsTXWuy72Wk...
To: Tom Rini trini@konsulko.com To: Heinrich Schuchardt xypron.glpk@gmx.de To: Ilias Apalodimas ilias.apalodimas@linaro.org To: Simon Glass sjg@chromium.org To: Mario Six mario.six@gdsys.cc To: Alper Nebi Yasak alpernebiyasak@gmail.com To: Abdellatif El Khlifi abdellatif.elkhlifi@arm.com Cc: Richard Hughes hughsient@gmail.com Cc: u-boot@lists.denx.de
Changes in v2: - Move namespace UUID to be defined in defconfig - Add tests and tooling - Only use the first board compatible to generate UUID. - Link to v1: https://lore.kernel.org/r/20240426-b4-dynamic-uuid-v1-0-e8154e00ec44@linaro....
--- Caleb Connolly (7): lib: uuid: add UUID v5 support efi: add a helper to generate dynamic UUIDs doc: uefi: document dynamic UUID generation sandbox: switch to dynamic UUIDs lib: uuid: supporting building as part of host tools tools: add genguid tool test: lib/uuid: add unit tests for dynamic UUIDs
arch/Kconfig | 1 + board/sandbox/sandbox.c | 16 --- configs/sandbox_defconfig | 1 + configs/sandbox_flattree_defconfig | 1 + doc/develop/uefi/uefi.rst | 31 +++++ include/sandbox_efi_capsule.h | 6 +- include/uuid.h | 21 ++- lib/Kconfig | 8 ++ lib/efi_loader/Kconfig | 23 +++ lib/efi_loader/efi_capsule.c | 1 + lib/efi_loader/efi_firmware.c | 66 +++++++++ lib/uuid.c | 81 +++++++++-- test/lib/uuid.c | 90 ++++++++++++ .../test_efi_capsule/test_capsule_firmware_fit.py | 2 +- .../test_efi_capsule/test_capsule_firmware_raw.py | 8 +- .../test_capsule_firmware_signed_fit.py | 2 +- .../test_capsule_firmware_signed_raw.py | 4 +- test/py/tests/test_efi_capsule/version.dts | 6 +- tools/Makefile | 3 + tools/binman/etype/efi_capsule.py | 2 +- tools/binman/ftest.py | 2 +- tools/genguid.c | 154 +++++++++++++++++++++ 22 files changed, 481 insertions(+), 48 deletions(-) --- change-id: 20240422-b4-dynamic-uuid-1a5ab1486c27 base-commit: 2e682a4a406fc81ef32e05c28542cc8067f1e15f
// Caleb (they/them)

Add support for generate version 5 UUIDs, these are determistic and work by hashing a "namespace" UUID together with some unique data. One intended usecase is to allow for dynamically generate payload UUIDs for UEFI capsule updates, so that supported boards can have their own UUIDs without needing to hardcode them.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- include/uuid.h | 17 +++++++++++++++++ lib/Kconfig | 8 ++++++++ lib/uuid.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+)
diff --git a/include/uuid.h b/include/uuid.h index f5a941250f48..539affaa47b9 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -10,8 +10,9 @@ #ifndef __UUID_H__ #define __UUID_H__
#include <linux/bitops.h> +#include <linux/kconfig.h>
/* * UUID - Universally Unique IDentifier - 128 bits unique number. * There are 5 versions and one variant of UUID defined by RFC4122 @@ -142,8 +143,24 @@ void gen_rand_uuid(unsigned char *uuid_bin); * @param - uuid output type: UUID - 0, GUID - 1 */ void gen_rand_uuid_str(char *uuid_str, int str_format);
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +/** + * gen_uuid_v5() - generate UUID v5 from namespace and other seed data. + * + * @namespace: pointer to UUID namespace salt + * @uuid: pointer to allocated UUID output + * @...: NULL terminated list of seed data as pairs of pointers + * to data and their lengths + */ +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...); +#else +static inline void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{ +} +#endif + /** * uuid_str_to_le_bin() - Convert string UUID to little endian binary data. * @uuid_str: pointer to UUID string * @uuid_bin: pointer to allocated array for little endian output [16B] diff --git a/lib/Kconfig b/lib/Kconfig index 189e6eb31aa1..2941532f25cf 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -80,8 +80,16 @@ config RANDOM_UUID help Enable the generation of partitions with random UUIDs if none are provided.
+config UUID_GEN_V5 + bool "Enable UUID version 5 generation" + select LIB_UUID + depends on SHA1 + help + Enable the generation of version 5 UUIDs, these are determistic and + generated from a namespace UUID, and a string (such as a board name). + config SPL_LIB_UUID depends on SPL bool
diff --git a/lib/uuid.c b/lib/uuid.c index dfa2320ba267..6ef006cca1da 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -21,8 +21,9 @@ #include <part_efi.h> #include <malloc.h> #include <dm/uclass.h> #include <rng.h> +#include <u-boot/sha1.h>
int uuid_str_valid(const char *uuid) { int i, valid; @@ -368,8 +369,44 @@ void uuid_bin_to_str(const unsigned char *uuid_bin, char *uuid_str, } } }
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{ + sha1_context ctx; + va_list args; + const uint8_t *data; + uint8_t hash[SHA1_SUM_LEN]; + uint32_t tmp; + + sha1_starts(&ctx); + /* Hash the namespace UUID as salt */ + sha1_update(&ctx, (unsigned char *)namespace, UUID_BIN_LEN); + va_start(args, uuid); + + while ((data = va_arg(args, const uint8_t *))) { + size_t len = va_arg(args, size_t); + + sha1_update(&ctx, data, len); + } + + va_end(args); + sha1_finish(&ctx, hash); + + /* Truncate the hash into output UUID, it is already big endian */ + memcpy(uuid, hash, sizeof(*uuid)); + + /* Configure variant/version bits */ + tmp = be32_to_cpu(uuid->time_hi_and_version); + tmp = (tmp & ~UUID_VERSION_MASK) | (5 << UUID_VERSION_SHIFT); + uuid->time_hi_and_version = cpu_to_be32(tmp); + + uuid->clock_seq_hi_and_reserved &= UUID_VARIANT_MASK; + uuid->clock_seq_hi_and_reserved |= UUID_VARIANT << UUID_VARIANT_SHIFT; +} +#endif + #if defined(CONFIG_RANDOM_UUID) || defined(CONFIG_CMD_UUID) void gen_rand_uuid(unsigned char *uuid_bin) { u32 ptr[4];

Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add support for generate version 5 UUIDs, these are determistic and work
spelling
by hashing a "namespace" UUID together with some unique data. One intended usecase is to allow for dynamically generate payload UUIDs for UEFI capsule updates, so that supported boards can have their own UUIDs without needing to hardcode them.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
include/uuid.h | 17 +++++++++++++++++ lib/Kconfig | 8 ++++++++ lib/uuid.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+)
Can you please mention the tests for this? I believe they are in your last commit?
diff --git a/include/uuid.h b/include/uuid.h index f5a941250f48..539affaa47b9 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -10,8 +10,9 @@ #ifndef __UUID_H__ #define __UUID_H__
#include <linux/bitops.h> +#include <linux/kconfig.h>
/*
- UUID - Universally Unique IDentifier - 128 bits unique number.
There are 5 versions and one variant of UUID defined by RFC4122
@@ -142,8 +143,24 @@ void gen_rand_uuid(unsigned char *uuid_bin);
- @param - uuid output type: UUID - 0, GUID - 1
*/ void gen_rand_uuid_str(char *uuid_str, int str_format);
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +/**
- gen_uuid_v5() - generate UUID v5 from namespace and other seed data.
- @namespace: pointer to UUID namespace salt
- @uuid: pointer to allocated UUID output
- @...: NULL terminated list of seed data as pairs of pointers
to data and their lengths
- */
+void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...); +#else +static inline void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{ +} +#endif
/**
- uuid_str_to_le_bin() - Convert string UUID to little endian binary data.
- @uuid_str: pointer to UUID string
- @uuid_bin: pointer to allocated array for little endian output [16B]
diff --git a/lib/Kconfig b/lib/Kconfig index 189e6eb31aa1..2941532f25cf 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -80,8 +80,16 @@ config RANDOM_UUID help Enable the generation of partitions with random UUIDs if none are provided.
+config UUID_GEN_V5
bool "Enable UUID version 5 generation"
select LIB_UUID
depends on SHA1
help
Enable the generation of version 5 UUIDs, these are determistic and
generated from a namespace UUID, and a string (such as a board name).
config SPL_LIB_UUID depends on SPL bool
diff --git a/lib/uuid.c b/lib/uuid.c index dfa2320ba267..6ef006cca1da 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -21,8 +21,9 @@ #include <part_efi.h> #include <malloc.h> #include <dm/uclass.h> #include <rng.h> +#include <u-boot/sha1.h>
int uuid_str_valid(const char *uuid) { int i, valid; @@ -368,8 +369,44 @@ void uuid_bin_to_str(const unsigned char *uuid_bin, char *uuid_str, } } }
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...)
Is this in a header file somewhere? It needs a function comment there.
+{
sha1_context ctx;
va_list args;
const uint8_t *data;
uint8_t hash[SHA1_SUM_LEN];
uint32_t tmp;
sha1_starts(&ctx);
/* Hash the namespace UUID as salt */
sha1_update(&ctx, (unsigned char *)namespace, UUID_BIN_LEN);
va_start(args, uuid);
while ((data = va_arg(args, const uint8_t *))) {
size_t len = va_arg(args, size_t);
sha1_update(&ctx, data, len);
}
va_end(args);
sha1_finish(&ctx, hash);
/* Truncate the hash into output UUID, it is already big endian */
memcpy(uuid, hash, sizeof(*uuid));
/* Configure variant/version bits */
tmp = be32_to_cpu(uuid->time_hi_and_version);
tmp = (tmp & ~UUID_VERSION_MASK) | (5 << UUID_VERSION_SHIFT);
uuid->time_hi_and_version = cpu_to_be32(tmp);
uuid->clock_seq_hi_and_reserved &= UUID_VARIANT_MASK;
uuid->clock_seq_hi_and_reserved |= UUID_VARIANT << UUID_VARIANT_SHIFT;
+} +#endif
#if defined(CONFIG_RANDOM_UUID) || defined(CONFIG_CMD_UUID) void gen_rand_uuid(unsigned char *uuid_bin) { u32 ptr[4];
-- 2.45.0
Regards, Simon

Hi Simon,
On 29/05/2024 18:30, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add support for generate version 5 UUIDs, these are determistic and work
spelling
Thanks
by hashing a "namespace" UUID together with some unique data. One intended usecase is to allow for dynamically generate payload UUIDs for UEFI capsule updates, so that supported boards can have their own UUIDs without needing to hardcode them.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
include/uuid.h | 17 +++++++++++++++++ lib/Kconfig | 8 ++++++++ lib/uuid.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+)
Can you please mention the tests for this? I believe they are in your last commit?
Yeah will do.
diff --git a/include/uuid.h b/include/uuid.h index f5a941250f48..539affaa47b9 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -10,8 +10,9 @@ #ifndef __UUID_H__ #define __UUID_H__
#include <linux/bitops.h> +#include <linux/kconfig.h>
/*
- UUID - Universally Unique IDentifier - 128 bits unique number.
There are 5 versions and one variant of UUID defined by RFC4122
@@ -142,8 +143,24 @@ void gen_rand_uuid(unsigned char *uuid_bin);
- @param - uuid output type: UUID - 0, GUID - 1
*/ void gen_rand_uuid_str(char *uuid_str, int str_format);
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +/**
- gen_uuid_v5() - generate UUID v5 from namespace and other seed data.
- @namespace: pointer to UUID namespace salt
- @uuid: pointer to allocated UUID output
- @...: NULL terminated list of seed data as pairs of pointers
to data and their lengths
- */
+void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...); +#else +static inline void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{ +} +#endif
[...]
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...)
Is this in a header file somewhere? It needs a function comment there.
Yes, see directly above where I add it to include/uuid.h
Kind regards,
+{
sha1_context ctx;
va_list args;
const uint8_t *data;
uint8_t hash[SHA1_SUM_LEN];
uint32_t tmp;
sha1_starts(&ctx);
/* Hash the namespace UUID as salt */
sha1_update(&ctx, (unsigned char *)namespace, UUID_BIN_LEN);
va_start(args, uuid);
while ((data = va_arg(args, const uint8_t *))) {
size_t len = va_arg(args, size_t);
sha1_update(&ctx, data, len);
}
va_end(args);
sha1_finish(&ctx, hash);
/* Truncate the hash into output UUID, it is already big endian */
memcpy(uuid, hash, sizeof(*uuid));
/* Configure variant/version bits */
tmp = be32_to_cpu(uuid->time_hi_and_version);
tmp = (tmp & ~UUID_VERSION_MASK) | (5 << UUID_VERSION_SHIFT);
uuid->time_hi_and_version = cpu_to_be32(tmp);
uuid->clock_seq_hi_and_reserved &= UUID_VARIANT_MASK;
uuid->clock_seq_hi_and_reserved |= UUID_VARIANT << UUID_VARIANT_SHIFT;
+} +#endif
- #if defined(CONFIG_RANDOM_UUID) || defined(CONFIG_CMD_UUID) void gen_rand_uuid(unsigned char *uuid_bin) { u32 ptr[4];
-- 2.45.0
Regards, Simon

Hi Caleb,
On Wed, 29 May 2024 at 12:55, Caleb Connolly caleb.connolly@linaro.org wrote:
Hi Simon,
On 29/05/2024 18:30, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add support for generate version 5 UUIDs, these are determistic and work
spelling
Thanks
by hashing a "namespace" UUID together with some unique data. One intended usecase is to allow for dynamically generate payload UUIDs for UEFI capsule updates, so that supported boards can have their own UUIDs without needing to hardcode them.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
include/uuid.h | 17 +++++++++++++++++ lib/Kconfig | 8 ++++++++ lib/uuid.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+)
Can you please mention the tests for this? I believe they are in your last commit?
Yeah will do.
diff --git a/include/uuid.h b/include/uuid.h index f5a941250f48..539affaa47b9 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -10,8 +10,9 @@ #ifndef __UUID_H__ #define __UUID_H__
#include <linux/bitops.h> +#include <linux/kconfig.h>
/*
- UUID - Universally Unique IDentifier - 128 bits unique number.
There are 5 versions and one variant of UUID defined by RFC4122
@@ -142,8 +143,24 @@ void gen_rand_uuid(unsigned char *uuid_bin);
- @param - uuid output type: UUID - 0, GUID - 1
*/ void gen_rand_uuid_str(char *uuid_str, int str_format);
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +/**
- gen_uuid_v5() - generate UUID v5 from namespace and other seed data.
- @namespace: pointer to UUID namespace salt
- @uuid: pointer to allocated UUID output
- @...: NULL terminated list of seed data as pairs of pointers
to data and their lengths
- */
+void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...); +#else +static inline void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{ +} +#endif
[...]
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...)
Is this in a header file somewhere? It needs a function comment there.
Yes, see directly above where I add it to include/uuid.h
OK thanks. We are using a different format now - the old @param stuff is in the past :-)
Regards, Simon

Hi Caleb,
diff --git a/lib/uuid.c b/lib/uuid.c index dfa2320ba267..6ef006cca1da 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -21,8 +21,9 @@ #include <part_efi.h> #include <malloc.h> #include <dm/uclass.h> #include <rng.h> +#include <u-boot/sha1.h>
int uuid_str_valid(const char *uuid) { int i, valid; @@ -368,8 +369,44 @@ void uuid_bin_to_str(const unsigned char *uuid_bin, char *uuid_str, } } }
+#if IS_ENABLED(CONFIG_UUID_GEN_V5) +void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) +{
sha1_context ctx;
va_list args;
const uint8_t *data;
uint8_t hash[SHA1_SUM_LEN];
uint32_t tmp;
sha1_starts(&ctx);
/* Hash the namespace UUID as salt */
sha1_update(&ctx, (unsigned char *)namespace, UUID_BIN_LEN);
va_start(args, uuid);
while ((data = va_arg(args, const uint8_t *))) {
size_t len = va_arg(args, size_t);
sha1_update takes an unsigned int so i'd prefer unsigned int len = .... etc
sha1_update(&ctx, data, len);
}
va_end(args);
sha1_finish(&ctx, hash);
/* Truncate the hash into output UUID, it is already big endian */
memcpy(uuid, hash, sizeof(*uuid));
/* Configure variant/version bits */
tmp = be32_to_cpu(uuid->time_hi_and_version);
tmp = (tmp & ~UUID_VERSION_MASK) | (5 << UUID_VERSION_SHIFT);
uuid->time_hi_and_version = cpu_to_be32(tmp);
uuid->clock_seq_hi_and_reserved &= UUID_VARIANT_MASK;
uuid->clock_seq_hi_and_reserved |= UUID_VARIANT << UUID_VARIANT_SHIFT;
+} +#endif
#if defined(CONFIG_RANDOM_UUID) || defined(CONFIG_CMD_UUID) void gen_rand_uuid(unsigned char *uuid_bin) { u32 ptr[4];
-- 2.45.0
Thanks /Ilias

Introduce a new helper efi_capsule_update_info_gen_ids() which populates the capsule update fw images image_type_id field. This allows for determinstic UUIDs to be used that can scale to a large number of different boards and board variants without the need to maintain a big list.
We call this from efi_fill_image_desc_array() to populate the UUIDs lazily on-demand.
This is behind an additional config option as it depends on V5 UUIDs and the SHA1 implementation.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- lib/efi_loader/Kconfig | 23 +++++++++++++++ lib/efi_loader/efi_capsule.c | 1 + lib/efi_loader/efi_firmware.c | 66 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+)
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index 430bb7f0f7dc..e90caf4f8e14 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -235,8 +235,31 @@ config EFI_CAPSULE_ON_DISK_EARLY If this option is enabled, capsules will be enforced to be executed as part of U-Boot initialisation so that they will surely take place whatever is set to distro_bootcmd.
+config EFI_CAPSULE_DYNAMIC_UUIDS + bool "Dynamic UUIDs for capsules" + depends on EFI_HAVE_CAPSULE_SUPPORT + select UUID_GEN_V5 + help + Select this option if you want to use dynamically generated v5 + UUIDs for your board. To make use of this feature, your board + code should call efi_capsule_update_info_gen_ids() with a seed + UUID to generate the image_type_id field for each fw_image. + + The CapsuleUpdate payloads are expected to generate matching UUIDs + using the same scheme. + +config EFI_CAPSULE_NAMESPACE_UUID + string "Namespace UUID for dynamic UUIDs" + depends on EFI_CAPSULE_DYNAMIC_UUIDS + help + Define the namespace or "salt" UUID used to generate the per-image + UUIDs. This should be a UUID in the standard 8-4-4-4-12 format. + + Device vendors are expected to generate their own namespace UUID + to avoid conflicts with existing products. + config EFI_CAPSULE_FIRMWARE bool
config EFI_CAPSULE_FIRMWARE_MANAGEMENT diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c index 0937800e588f..ac02e79ae7d8 100644 --- a/lib/efi_loader/efi_capsule.c +++ b/lib/efi_loader/efi_capsule.c @@ -19,8 +19,9 @@ #include <mapmem.h> #include <sort.h> #include <sysreset.h> #include <asm/global_data.h> +#include <uuid.h>
#include <crypto/pkcs7.h> #include <crypto/pkcs7_parser.h> #include <linux/err.h> diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index ba5aba098c0f..a8dafe4f01a5 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -244,8 +244,71 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_
free(var_state); }
+#if CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) +/** + * efi_capsule_update_info_gen_ids - generate GUIDs for the images + * + * Generate the image_type_id for each image in the update_info.images array + * using the first compatible from the device tree and a salt + * UUID defined at build time. + * + * Returns: status code + */ +static efi_status_t efi_capsule_update_info_gen_ids(void) +{ + int ret, i; + struct uuid namespace; + const char *compatible; /* Full array including null bytes */ + struct efi_fw_image *fw_array; + + fw_array = update_info.images; + /* Check if we need to run (there are images and we didn't already generate their IDs) */ + if (!update_info.num_images || + memchr_inv(&fw_array[0].image_type_id, 0, sizeof(fw_array[0].image_type_id))) + return EFI_SUCCESS; + + ret = uuid_str_to_bin(CONFIG_EFI_CAPSULE_NAMESPACE_UUID, + (unsigned char *)&namespace, UUID_STR_FORMAT_GUID); + if (ret) { + log_debug("%s: CONFIG_EFI_CAPSULE_NAMESPACE_UUID is invalid: %d\n", __func__, ret); + return EFI_UNSUPPORTED; + } + + compatible = ofnode_read_string(ofnode_root(), "compatible"); + + if (!compatible) { + log_debug("%s: model or compatible not defined\n", __func__); + return EFI_UNSUPPORTED; + } + + if (!update_info.num_images) { + log_debug("%s: no fw_images, make sure update_info.num_images is set\n", __func__); + return -ENODATA; + } + + for (i = 0; i < update_info.num_images; i++) { + gen_uuid_v5(&namespace, + (struct uuid *)&fw_array[i].image_type_id, + compatible, strlen(compatible), + fw_array[i].fw_name, u16_strsize(fw_array[i].fw_name) + - sizeof(uint16_t), + NULL); + + log_debug("Image %ls UUID %pUs\n", fw_array[i].fw_name, + &fw_array[i].image_type_id); + } + + return EFI_SUCCESS; +} +#else +static efi_status_t efi_capsule_update_info_gen_ids(void) +{ + return EFI_SUCCESS; +} +#endif + /** * efi_fill_image_desc_array - populate image descriptor array * @image_info_size: Size of @image_info * @image_info: Image information @@ -282,8 +345,11 @@ static efi_status_t efi_fill_image_desc_array( return EFI_BUFFER_TOO_SMALL; } *image_info_size = total_size;
+ if (efi_capsule_update_info_gen_ids() != EFI_SUCCESS) + return EFI_UNSUPPORTED; + fw_array = update_info.images; *descriptor_count = update_info.num_images; *descriptor_version = EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION; *descriptor_size = sizeof(*image_info);

Hi Caleb,
[...]
#include <crypto/pkcs7.h> #include <crypto/pkcs7_parser.h> #include <linux/err.h> diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index ba5aba098c0f..a8dafe4f01a5 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -244,8 +244,71 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_
free(var_state);
}
+#if CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) +/**
- efi_capsule_update_info_gen_ids - generate GUIDs for the images
- Generate the image_type_id for each image in the update_info.images array
- using the first compatible from the device tree and a salt
- UUID defined at build time.
- Returns: status code
- */
+static efi_status_t efi_capsule_update_info_gen_ids(void) +{
int ret, i;
struct uuid namespace;
const char *compatible; /* Full array including null bytes */
struct efi_fw_image *fw_array;
fw_array = update_info.images;
/* Check if we need to run (there are images and we didn't already generate their IDs) */
if (!update_info.num_images ||
memchr_inv(&fw_array[0].image_type_id, 0, sizeof(fw_array[0].image_type_id)))
Why not just go a guidcmp()? memchr_inv() will return the first invalid match, but we don't need that
return EFI_SUCCESS;
ret = uuid_str_to_bin(CONFIG_EFI_CAPSULE_NAMESPACE_UUID,
(unsigned char *)&namespace, UUID_STR_FORMAT_GUID);
if (ret) {
log_debug("%s: CONFIG_EFI_CAPSULE_NAMESPACE_UUID is invalid: %d\n", __func__, ret);
return EFI_UNSUPPORTED;
}
compatible = ofnode_read_string(ofnode_root(), "compatible");
if (!compatible) {
log_debug("%s: model or compatible not defined\n", __func__);
return EFI_UNSUPPORTED;
}
if (!update_info.num_images) {
log_debug("%s: no fw_images, make sure update_info.num_images is set\n", __func__);
return -ENODATA;
}
[...]
Cheers /Ilias

On 30/05/2024 16:56, Ilias Apalodimas wrote:
Hi Caleb,
Hi Ilias,
[...]
#include <crypto/pkcs7.h> #include <crypto/pkcs7_parser.h> #include <linux/err.h> diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index ba5aba098c0f..a8dafe4f01a5 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -244,8 +244,71 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_
free(var_state);
}
+#if CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) +/**
- efi_capsule_update_info_gen_ids - generate GUIDs for the images
- Generate the image_type_id for each image in the update_info.images array
- using the first compatible from the device tree and a salt
- UUID defined at build time.
- Returns: status code
- */
+static efi_status_t efi_capsule_update_info_gen_ids(void) +{
int ret, i;
struct uuid namespace;
const char *compatible; /* Full array including null bytes */
struct efi_fw_image *fw_array;
fw_array = update_info.images;
/* Check if we need to run (there are images and we didn't already generate their IDs) */
if (!update_info.num_images ||
memchr_inv(&fw_array[0].image_type_id, 0, sizeof(fw_array[0].image_type_id)))
Why not just go a guidcmp()? memchr_inv() will return the first invalid match, but we don't need that
guidcmp() would require allocating a zero guid to compare to, this is just a check for any non-zero byte in the GUID so memchr_inv() seemed more fitting.
I can switch to guidcmp() for readability.
return EFI_SUCCESS;
ret = uuid_str_to_bin(CONFIG_EFI_CAPSULE_NAMESPACE_UUID,
(unsigned char *)&namespace, UUID_STR_FORMAT_GUID);
if (ret) {
log_debug("%s: CONFIG_EFI_CAPSULE_NAMESPACE_UUID is invalid: %d\n", __func__, ret);
return EFI_UNSUPPORTED;
}
compatible = ofnode_read_string(ofnode_root(), "compatible");
if (!compatible) {
log_debug("%s: model or compatible not defined\n", __func__);
return EFI_UNSUPPORTED;
}
if (!update_info.num_images) {
log_debug("%s: no fw_images, make sure update_info.num_images is set\n", __func__);
return -ENODATA;
}
[...]
Cheers /Ilias

On Fri, 31 May 2024 at 14:26, Caleb Connolly caleb.connolly@linaro.org wrote:
On 30/05/2024 16:56, Ilias Apalodimas wrote:
Hi Caleb,
Hi Ilias,
[...]
#include <crypto/pkcs7.h> #include <crypto/pkcs7_parser.h> #include <linux/err.h> diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index ba5aba098c0f..a8dafe4f01a5 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -244,8 +244,71 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_
free(var_state);
}
+#if CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) +/**
- efi_capsule_update_info_gen_ids - generate GUIDs for the images
- Generate the image_type_id for each image in the update_info.images array
- using the first compatible from the device tree and a salt
- UUID defined at build time.
- Returns: status code
- */
+static efi_status_t efi_capsule_update_info_gen_ids(void) +{
int ret, i;
struct uuid namespace;
const char *compatible; /* Full array including null bytes */
struct efi_fw_image *fw_array;
fw_array = update_info.images;
/* Check if we need to run (there are images and we didn't already generate their IDs) */
if (!update_info.num_images ||
memchr_inv(&fw_array[0].image_type_id, 0, sizeof(fw_array[0].image_type_id)))
Why not just go a guidcmp()? memchr_inv() will return the first invalid match, but we don't need that
guidcmp() would require allocating a zero guid to compare to, this is just a check for any non-zero byte in the GUID so memchr_inv() seemed more fitting.
I can switch to guidcmp() for readability.
Right, I misread what memchr_inv() does, keep it as-is it's fine
Thanks /Ilias
return EFI_SUCCESS;
ret = uuid_str_to_bin(CONFIG_EFI_CAPSULE_NAMESPACE_UUID,
(unsigned char *)&namespace, UUID_STR_FORMAT_GUID);
if (ret) {
log_debug("%s: CONFIG_EFI_CAPSULE_NAMESPACE_UUID is invalid: %d\n", __func__, ret);
return EFI_UNSUPPORTED;
}
compatible = ofnode_read_string(ofnode_root(), "compatible");
if (!compatible) {
log_debug("%s: model or compatible not defined\n", __func__);
return EFI_UNSUPPORTED;
}
if (!update_info.num_images) {
log_debug("%s: no fw_images, make sure update_info.num_images is set\n", __func__);
return -ENODATA;
}
[...]
Cheers /Ilias
-- // Caleb (they/them)

Document how platforms can generate GUIDs at runtime rather than maintaining a list of UUIDs per-board.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- doc/develop/uefi/uefi.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 0389b269c01b..0b60702c052a 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -448,8 +448,39 @@ the location of the firmware updates is not a very secure practice. Getting this information from the firmware itself is more secure, assuming the firmware has been verified by a previous stage boot loader.
+The image_type_id contains a GUID value which is specific to the image +and board being updated, that is to say it should uniquely identify the +board model (and revision if relevant) and image pair. Traditionally, +these GUIDs are generated manually and hardcoded on a per-board basis, +however this scheme makes it difficult to scale up to support many +boards. + +To address this, v5 GUIDs can be used to generate board-specific GUIDs +at runtime, based on a set of persistent identifiable information: + +.. code-block:: c + + /** + * efi_capsule_update_info_gen_ids - generate GUIDs for the images + * + * Generate the image_type_id for each image in the update_info.images array + * using the model and compatible strings from the device tree and a salt + * UUID defined at build time. + * + * Returns: status code + */ + static efi_status_t efi_capsule_update_info_gen_ids(void); + +These strings are combined with the fw_image name to generate GUIDs for +each image. Support for dynamic UUIDs can be enabled by turning on +CONFIG_EFI_CAPSULE_DYNAMIC_UUIDS, generating a new namespace UUID and +setting CONFIG_EFI_CAPSULE_NAMESPACE_UUID to it. + +The genguid tool can be used to determine the GUIDs for a particular board +and image. It can be found in the tools directory. + The firmware images structure defines the GUID values, image index values and the name of the images that are to be updated through the capsule update feature. These values are to be defined as part of an array. These GUID values would be used by the Firmware Management

On Wed, 29 May 2024 at 17:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Document how platforms can generate GUIDs at runtime rather than maintaining a list of UUIDs per-board.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
doc/develop/uefi/uefi.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 0389b269c01b..0b60702c052a 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -448,8 +448,39 @@ the location of the firmware updates is not a very secure practice. Getting this information from the firmware itself is more secure, assuming the firmware has been verified by a previous stage boot loader.
+The image_type_id contains a GUID value which is specific to the image +and board being updated, that is to say it should uniquely identify the +board model (and revision if relevant) and image pair. Traditionally, +these GUIDs are generated manually and hardcoded on a per-board basis, +however this scheme makes it difficult to scale up to support many +boards.
+To address this, v5 GUIDs can be used to generate board-specific GUIDs +at runtime, based on a set of persistent identifiable information:
+.. code-block:: c
/**
* efi_capsule_update_info_gen_ids - generate GUIDs for the images
*
* Generate the image_type_id for each image in the
update_info.images array
* using the model and compatible strings from the device tree and
a salt
* UUID defined at build time.
*
* Returns: status code
*/
static efi_status_t efi_capsule_update_info_gen_ids(void);
+These strings are combined with the fw_image name to generate GUIDs for +each image. Support for dynamic UUIDs can be enabled by turning on +CONFIG_EFI_CAPSULE_DYNAMIC_UUIDS, generating a new namespace UUID and +setting CONFIG_EFI_CAPSULE_NAMESPACE_UUID to it.
+The genguid tool can be used to determine the GUIDs for a particular board +and image. It can be found in the tools directory.
The firmware images structure defines the GUID values, image index values and the name of the images that are to be updated through the capsule update feature. These values are to be defined as part of an array. These GUID values would be used by the Firmware Management
-- 2.45.0
Reviewed-by: Ilias Apalodimas ilias.apalodimas@linaro.org

Migrate sandbox over to generating it's capsule update image GUIDs dynamically from the namespace and board/image info. Update the reference and tests to use the new GUIDs.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- arch/Kconfig | 1 + board/sandbox/sandbox.c | 16 ---------------- configs/sandbox_defconfig | 1 + configs/sandbox_flattree_defconfig | 1 + include/sandbox_efi_capsule.h | 6 +++--- .../tests/test_efi_capsule/test_capsule_firmware_fit.py | 2 +- .../tests/test_efi_capsule/test_capsule_firmware_raw.py | 8 ++++---- .../test_efi_capsule/test_capsule_firmware_signed_fit.py | 2 +- .../test_efi_capsule/test_capsule_firmware_signed_raw.py | 4 ++-- test/py/tests/test_efi_capsule/version.dts | 6 +++--- tools/binman/etype/efi_capsule.py | 2 +- tools/binman/ftest.py | 2 +- 12 files changed, 19 insertions(+), 32 deletions(-)
diff --git a/arch/Kconfig b/arch/Kconfig index abd406d48841..0558c90540b6 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -164,8 +164,9 @@ config SANDBOX select SYS_CACHE_SHIFT_4 select IRQ select SUPPORT_EXTENSION_SCAN if CMDLINE select SUPPORT_ACPI + select EFI_CAPSULE_DYNAMIC_UUIDS if EFI_HAVE_CAPSULE_SUPPORT imply BITREVERSE select BLOBLIST imply LTO imply CMD_DM diff --git a/board/sandbox/sandbox.c b/board/sandbox/sandbox.c index 802596569c64..d97945e58fcf 100644 --- a/board/sandbox/sandbox.c +++ b/board/sandbox/sandbox.c @@ -31,36 +31,20 @@ */ gd_t *gd;
#if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) -/* GUIDs for capsule updatable firmware images */ -#define SANDBOX_UBOOT_IMAGE_GUID \ - EFI_GUID(0x09d7cf52, 0x0720, 0x4710, 0x91, 0xd1, \ - 0x08, 0x46, 0x9b, 0x7f, 0xe9, 0xc8) - -#define SANDBOX_UBOOT_ENV_IMAGE_GUID \ - EFI_GUID(0x5a7021f5, 0xfef2, 0x48b4, 0xaa, 0xba, \ - 0x83, 0x2e, 0x77, 0x74, 0x18, 0xc0) - -#define SANDBOX_FIT_IMAGE_GUID \ - EFI_GUID(0x3673b45d, 0x6a7c, 0x46f3, 0x9e, 0x60, \ - 0xad, 0xab, 0xb0, 0x3f, 0x79, 0x37) - struct efi_fw_image fw_images[] = { #if defined(CONFIG_EFI_CAPSULE_FIRMWARE_RAW) { - .image_type_id = SANDBOX_UBOOT_IMAGE_GUID, .fw_name = u"SANDBOX-UBOOT", .image_index = 1, }, { - .image_type_id = SANDBOX_UBOOT_ENV_IMAGE_GUID, .fw_name = u"SANDBOX-UBOOT-ENV", .image_index = 2, }, #elif defined(CONFIG_EFI_CAPSULE_FIRMWARE_FIT) { - .image_type_id = SANDBOX_FIT_IMAGE_GUID, .fw_name = u"SANDBOX-FIT", .image_index = 1, }, #endif diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 93b52f2de5cf..58775b271600 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -349,8 +349,9 @@ CONFIG_TPM=y CONFIG_ERRNO_STR=y CONFIG_GETOPT=y CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y CONFIG_EFI_CAPSULE_ON_DISK=y +CONFIG_EFI_CAPSULE_NAMESPACE_UUID="09D7CF52-0720-4710-91D1-08469B7FE9C8" CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y CONFIG_EFI_CAPSULE_AUTHENTICATE=y CONFIG_EFI_CAPSULE_ESL_FILE="board/sandbox/capsule_pub_esl_good.esl" CONFIG_EFI_SECURE_BOOT=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index 6bf8874e722e..85ae63da8881 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -224,8 +224,9 @@ CONFIG_TPM=y CONFIG_ZSTD=y CONFIG_ERRNO_STR=y CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y CONFIG_EFI_CAPSULE_ON_DISK=y +CONFIG_EFI_CAPSULE_NAMESPACE_UUID="09d7cf52-0720-4710-91d1-08469b7fe9c8" CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_EFI_CAPSULE_AUTHENTICATE=y CONFIG_EFI_CAPSULE_ESL_FILE="board/sandbox/capsule_pub_esl_good.esl" CONFIG_UNIT_TEST=y diff --git a/include/sandbox_efi_capsule.h b/include/sandbox_efi_capsule.h index 3e288e8a84a2..25ac496ea24f 100644 --- a/include/sandbox_efi_capsule.h +++ b/include/sandbox_efi_capsule.h @@ -5,11 +5,11 @@
#if !defined(_SANDBOX_EFI_CAPSULE_H_) #define _SANDBOX_EFI_CAPSULE_H_
-#define SANDBOX_UBOOT_IMAGE_GUID "09d7cf52-0720-4710-91d1-08469b7fe9c8" -#define SANDBOX_UBOOT_ENV_IMAGE_GUID "5a7021f5-fef2-48b4-aaba-832e777418c0" -#define SANDBOX_FIT_IMAGE_GUID "3673b45d-6a7c-46f3-9e60-adabb03f7937" +#define SANDBOX_UBOOT_IMAGE_GUID "fd5db83c-12f3-a46b-80a9-e3007c7ff56e" +#define SANDBOX_UBOOT_ENV_IMAGE_GUID "935fe837-fac8-4394-c008-737d8852c60d" +#define SANDBOX_FIT_IMAGE_GUID "ffd97379-0956-fa94-c003-8bfcf5cc097b" #define SANDBOX_INCORRECT_GUID "058b7d83-50d5-4c47-a195-60d86ad341c4"
#define UBOOT_FIT_IMAGE "u-boot_bin_env.itb"
diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py index 11bcdc2bb293..746da4602085 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py @@ -146,9 +146,9 @@ class TestEfiCapsuleFirmwareFit(): verify_content(u_boot_console, '100000', 'u-boot:Old') verify_content(u_boot_console, '150000', 'u-boot-env:Old') else: # ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT. - assert '3673B45D-6A7C-46F3-9E60-ADABB03F7937' in ''.join(output) + assert '5AF91295-5A99-F62B-80D7-E9574DE87170' in ''.join(output) assert 'ESRT: fw_version=5' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
verify_content(u_boot_console, '100000', 'u-boot:New') diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py index a5b5c8a3853a..1866b8086573 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py @@ -133,12 +133,12 @@ class TestEfiCapsuleFirmwareRaw: 'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"', 'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT. - assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output) + assert '935FE837-FAC8-4394-C008-737D8852C60D' in ''.join(output)
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT. - assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output) + assert 'FD5DB83C-12F3-A46B-80A9-E3007C7FF56E' in ''.join(output)
check_file_removed(u_boot_console, disk_img, capsule_files)
expected = 'u-boot:Old' if capsule_auth else 'u-boot:New' @@ -187,14 +187,14 @@ class TestEfiCapsuleFirmwareRaw: verify_content(u_boot_console, '100000', 'u-boot:Old') verify_content(u_boot_console, '150000', 'u-boot-env:Old') else: # ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT. - assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output) + assert 'FD5DB83C-12F3-A46B-80A9-E3007C7FF56E' in ''.join(output) assert 'ESRT: fw_version=5' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT. - assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output) + assert '935FE837-FAC8-4394-C008-737D8852C60D' in ''.join(output) assert 'ESRT: fw_version=10' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=7' in ''.join(output)
verify_content(u_boot_console, '100000', 'u-boot:New') diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py index 44a58baa3106..a4e0a3bc73f5 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py @@ -156,9 +156,9 @@ class TestEfiCapsuleFirmwareSignedFit(): 'u-boot-env raw 0x150000 0x200000"', 'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT. - assert '3673B45D-6A7C-46F3-9E60-ADABB03F7937' in ''.join(output) + assert 'FD5DB83C-12F3-A46B-80A9-E3007C7FF56E' in ''.join(output) assert 'ESRT: fw_version=5' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
verify_content(u_boot_console, '100000', 'u-boot:New') diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py index 83a10e160b8c..260c71860632 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py @@ -150,14 +150,14 @@ class TestEfiCapsuleFirmwareSignedRaw(): 'u-boot-env raw 0x150000 0x200000"', 'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT. - assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output) + assert 'FD5DB83C-12F3-A46B-80A9-E3007C7FF56E' in ''.join(output) assert 'ESRT: fw_version=5' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT. - assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output) + assert '935FE837-FAC8-4394-C008-737D8852C60D' in ''.join(output) assert 'ESRT: fw_version=10' in ''.join(output) assert 'ESRT: lowest_supported_fw_version=7' in ''.join(output)
verify_content(u_boot_console, '100000', 'u-boot:New') diff --git a/test/py/tests/test_efi_capsule/version.dts b/test/py/tests/test_efi_capsule/version.dts index 07850cc6064c..3f0698bf7280 100644 --- a/test/py/tests/test_efi_capsule/version.dts +++ b/test/py/tests/test_efi_capsule/version.dts @@ -7,18 +7,18 @@ firmware-version { image1 { lowest-supported-version = <3>; image-index = <1>; - image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8"; + image-type-id = "FD5DB83C-12F3-A46B-80A9-E3007C7FF56E"; }; image2 { lowest-supported-version = <7>; image-index = <2>; - image-type-id = "5A7021F5-FEF2-48B4-AABA-832E777418C0"; + image-type-id = "935FE837-FAC8-4394-C008-737D8852C60D"; }; image3 { lowest-supported-version = <3>; image-index = <1>; - image-type-id = "3673B45D-6A7C-46F3-9E60-ADABB03F7937"; + image-type-id = "FFD97379-0956-FA94-C003-8BFCF5CC097B"; }; }; }; diff --git a/tools/binman/etype/efi_capsule.py b/tools/binman/etype/efi_capsule.py index e32037178221..da1f9b0a381a 100644 --- a/tools/binman/etype/efi_capsule.py +++ b/tools/binman/etype/efi_capsule.py @@ -23,9 +23,9 @@ def get_binman_test_guid(type_str): Returns: The actual GUID value (str) """ TYPE_TO_GUID = { - 'binman-test' : '09d7cf52-0720-4710-91d1-08469b7fe9c8' + 'binman-test' : 'fd5db83c-12f3-a46b-80a9-e3007c7ff56e' }
return TYPE_TO_GUID[type_str]
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 8a44bc051b36..dc602b95ecd5 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -123,9 +123,9 @@ TEE_ADDR = 0x5678
# Firmware Management Protocol(FMP) GUID FW_MGMT_GUID = '6dcbd5ed-e82d-4c44-bda1-7194199ad92a' # Image GUID specified in the DTS -CAPSULE_IMAGE_GUID = '09d7cf52-0720-4710-91d1-08469b7fe9c8' +CAPSULE_IMAGE_GUID = 'fd5db83c-12f3-a46b-80a9-e3007c7ff56e' # Windows cert GUID WIN_CERT_TYPE_EFI_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7' # Empty capsule GUIDs EMPTY_CAPSULE_ACCEPT_GUID = '0c996046-bcc0-4d04-85ec-e1fcedf1c6f8'

Adjust the UUID library code so that it can be compiled as part of a host tool.
This removes the one redundant log_debug() call, as well as the incorrectly defined LOG_CATEGORY.
In general this is a fairly trivial change, just adjusting includes and disabling list_guid.
This will be used by a new genguid tool to generate v5 GUIDs that match those generated by U-Boot at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- include/uuid.h | 4 ++-- lib/uuid.c | 44 ++++++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/include/uuid.h b/include/uuid.h index 539affaa47b9..b38b20d957ef 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -69,10 +69,10 @@ struct uuid { } __packed;
/* Bits of a bitmask specifying the output format for GUIDs */ #define UUID_STR_FORMAT_STD 0 -#define UUID_STR_FORMAT_GUID BIT(0) -#define UUID_STR_UPPER_CASE BIT(1) +#define UUID_STR_FORMAT_GUID 0x1 +#define UUID_STR_UPPER_CASE 0x2
/* Use UUID_STR_LEN + 1 for string space */ #define UUID_STR_LEN 36 #define UUID_BIN_LEN sizeof(struct uuid) diff --git a/lib/uuid.c b/lib/uuid.c index 6ef006cca1da..b6ad3dd06f6f 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -6,25 +6,38 @@ * Authors: * Abdellatif El Khlifi abdellatif.elkhlifi@arm.com */
-#define LOG_CATEGOT LOGC_CORE - +#ifndef USE_HOSTCC #include <command.h> #include <efi_api.h> #include <env.h> #include <rand.h> #include <time.h> -#include <uuid.h> -#include <linux/ctype.h> -#include <errno.h> #include <asm/io.h> #include <part_efi.h> #include <malloc.h> #include <dm/uclass.h> #include <rng.h> +#include <linux/ctype.h> +#include <hexdump.h> +#else +#include <stdarg.h> +#include <stdint.h> +#include <eficapsule.h> +#include <ctype.h> +#endif +#include <linux/types.h> +#include <errno.h> +#include <linux/kconfig.h> +#include <uuid.h> #include <u-boot/sha1.h>
+#ifdef USE_HOSTCC +/* polyfill hextoul to avoid pulling in strto.c */ +#define hextoul(cp, endp) strtoul(cp, endp, 16) +#endif + int uuid_str_valid(const char *uuid) { int i, valid;
@@ -51,8 +64,9 @@ int uuid_str_valid(const char *uuid) static const struct { const char *string; efi_guid_t guid; } list_guid[] = { +#ifndef USE_HOSTCC #ifdef CONFIG_PARTITION_TYPE_GUID {"system", PARTITION_SYSTEM_GUID}, {"mbr", LEGACY_MBR_PARTITION_GUID}, {"msft", PARTITION_MSFT_RESERVED_GUID}, @@ -231,8 +245,9 @@ static const struct { { "EFI_MEMORY_TYPE", EFI_MEMORY_TYPE }, { "EFI_MEM_STATUS_CODE_REC", EFI_MEM_STATUS_CODE_REC }, { "EFI_GUID_EFI_ACPI1", EFI_GUID_EFI_ACPI1 }, #endif +#endif /* !USE_HOSTCC */ };
int uuid_guid_get_bin(const char *guid_str, unsigned char *guid_bin) { @@ -266,9 +281,8 @@ int uuid_str_to_bin(const char *uuid_str, unsigned char *uuid_bin, uint32_t tmp32; uint64_t tmp64;
if (!uuid_str_valid(uuid_str)) { - log_debug("not valid\n"); #ifdef CONFIG_PARTITION_TYPE_GUID if (!uuid_guid_get_bin(uuid_str, uuid_bin)) return 0; #endif @@ -297,19 +311,19 @@ int uuid_str_to_bin(const char *uuid_str, unsigned char *uuid_bin,
tmp16 = cpu_to_be16(hextoul(uuid_str + 19, NULL)); memcpy(uuid_bin + 8, &tmp16, 2);
- tmp64 = cpu_to_be64(simple_strtoull(uuid_str + 24, NULL, 16)); + tmp64 = cpu_to_be64(hextoul(uuid_str + 24, NULL)); memcpy(uuid_bin + 10, (char *)&tmp64 + 2, 6);
return 0; }
int uuid_str_to_le_bin(const char *uuid_str, unsigned char *uuid_bin) { - u16 tmp16; - u32 tmp32; - u64 tmp64; + uint16_t tmp16; + uint32_t tmp32; + uint64_t tmp64;
if (!uuid_str_valid(uuid_str) || !uuid_bin) return -EINVAL;
@@ -324,22 +338,22 @@ int uuid_str_to_le_bin(const char *uuid_str, unsigned char *uuid_bin)
tmp16 = cpu_to_le16(hextoul(uuid_str + 19, NULL)); memcpy(uuid_bin + 8, &tmp16, 2);
- tmp64 = cpu_to_le64(simple_strtoull(uuid_str + 24, NULL, 16)); + tmp64 = cpu_to_le64(hextoul(uuid_str + 24, NULL)); memcpy(uuid_bin + 10, &tmp64, 6);
return 0; }
void uuid_bin_to_str(const unsigned char *uuid_bin, char *uuid_str, int str_format) { - const u8 uuid_char_order[UUID_BIN_LEN] = {0, 1, 2, 3, 4, 5, 6, 7, 8, + const uint8_t uuid_char_order[UUID_BIN_LEN] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - const u8 guid_char_order[UUID_BIN_LEN] = {3, 2, 1, 0, 5, 4, 7, 6, 8, + const uint8_t guid_char_order[UUID_BIN_LEN] = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15}; - const u8 *char_order; + const uint8_t *char_order; const char *format; int i;
/* @@ -405,8 +419,9 @@ void gen_uuid_v5(const struct uuid *namespace, struct uuid *uuid, ...) uuid->clock_seq_hi_and_reserved |= UUID_VARIANT << UUID_VARIANT_SHIFT; } #endif
+#ifndef USE_HOSTCC #if defined(CONFIG_RANDOM_UUID) || defined(CONFIG_CMD_UUID) void gen_rand_uuid(unsigned char *uuid_bin) { u32 ptr[4]; @@ -494,4 +509,5 @@ U_BOOT_CMD(guid, CONFIG_SYS_MAXARGS, 1, do_uuid, "e.g. guid guid_env" ); #endif /* CONFIG_CMD_UUID */ #endif /* CONFIG_RANDOM_UUID || CONFIG_CMD_UUID */ +#endif /* !USE_HOSTCC */

Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
diff --git a/tools/Makefile b/tools/Makefile index 6a4280e3668f..7db7723793d5 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -253,8 +253,11 @@ HOSTLDLIBS_mkeficapsule += \ HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule
+genguid-objs := generated/lib/uuid.o generated/lib/sha1.o genguid.o +hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += genguid + mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
diff --git a/tools/genguid.c b/tools/genguid.c new file mode 100644 index 000000000000..dbac65d42623 --- /dev/null +++ b/tools/genguid.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2024 Linaro Ltd. + * Author: Caleb Connolly + */ + +#include <getopt.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <linux/types.h> + +#include <uuid.h> + +static struct option options[] = { + {"dtb", required_argument, NULL, 'd'}, + {"compat", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {"json", no_argument, NULL, 'j'}, + {NULL, 0, NULL, 0}, +}; + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n", progname); + fprintf(stderr, + "Generate a v5 GUID for one of more U-Boot fw_images the same way U-Boot does at runtime.\n"); + fprintf(stderr, + "\nOptions:\n" + " GUID namespace/salt GUID in 8-4-4-4-12 format\n" + " -h, --help display this help and exit\n" + " -c, --compat=COMPAT first compatible property in the board devicetree\n" + " -v, --verbose print debug messages\n" + " -j, --json output in JSON format\n" + " NAME... one or more names of fw_images to generate GUIDs for\n" + ); + fprintf(stderr, "\nExample:\n"); + fprintf(stderr, " %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\n" + "\t-c "qcom,qrb4210-rb2" \\n" + "\tQUALCOMM-UBOOT\n", progname); +} + +size_t u16_strsize(const uint16_t *in) +{ + size_t i = 0, count = UINT16_MAX; + + while (count-- && in[i]) + i++; + + return (i + 1) * sizeof(uint16_t); +} + +int main(int argc, char **argv) +{ + struct uuid namespace; + char *namespace_str; + char uuid_str[37]; + char **image_uuids; + char *compatible = NULL; + uint16_t **images_u16; + char **images; + int c, n_images; + bool debug = false, json = false; + + if (argc < 2) { + usage(argv[0]); + return 1; + } + + namespace_str = argv[1]; + + /* The first arg is the GUID so skip it */ + while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) != -1) { + switch (c) { + case 'c': + compatible = strdup(optarg); + break; + case 'h': + usage(argv[0]); + return 0; + case 'v': + debug = true; + break; + case 'j': + json = true; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (!compatible) { + fprintf(stderr, "ERROR: Please specify the compatible property.\n\n"); + usage(argv[0]); + return 1; + } + + if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace, UUID_STR_FORMAT_GUID)) { + fprintf(stderr, "ERROR: Check that your UUID is formatted correctly.\n"); + exit(EXIT_FAILURE); + } + + /* This is probably not the best way to convert a string to a "u16" string */ + n_images = argc - optind - 1; + images = argv + optind + 1; + images_u16 = calloc(n_images, sizeof(char *)); + for (int i = 0; i < n_images; i++) { + images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2); + for (int j = 0; j < strlen(images[i]); j++) + images_u16[i][j] = (uint16_t)images[i][j]; + } + + if (debug) { + fprintf(stderr, "GUID: "); + uuid_bin_to_str((uint8_t *)&namespace, uuid_str, UUID_STR_FORMAT_GUID); + fprintf(stderr, "%s\n", uuid_str); + fprintf(stderr, "Compatible: "%s"\n", compatible); + fprintf(stderr, "Images: "); + for (int i = 0; i < n_images; i++) + fprintf(stderr, ""%s"%s", argv[optind + i + 1], + i == n_images - 1 ? "\n" : ", "); + } + + image_uuids = calloc(n_images, sizeof(char *)); + for (int i = 0; i < n_images; i++) { + struct uuid image_type_id; + + gen_uuid_v5(&namespace, &image_type_id, + compatible, strlen(compatible), + images_u16[i], u16_strsize(images_u16[i]) - sizeof(uint16_t), + NULL); + + uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str, UUID_STR_FORMAT_GUID); + image_uuids[i] = strdup(uuid_str); + } + + if (json) { + printf("[\n"); + for (int i = 0; i < n_images; i++) + printf("\t{"name": "%s", "uuid": "%s"}%s\n", images[i], image_uuids[i], + i == n_images - 1 ? "" : ","); + printf("]\n"); + } else { + for (int i = 0; i < n_images; i++) + printf("%-24s| %s\n", images[i], image_uuids[i]); + } + + return 0; +} +

Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
Where is this tool used? Can you add a man page and test?
Regards, Simon

Hi Simon,
On 29/05/2024 18:30, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
Where is this tool used? Can you add a man page and test?
It currently doesn't have any users, it just allows for pre-generating GUIDs ahead of time for a given board. It might be hooked up to vendor tooling or some usecase like that.
I've somehow escaped learning Groff before now heh, I can give it a go and write a test.
Regards, Simon

Hi Caleb,
On Wed, 29 May 2024 at 13:02, Caleb Connolly caleb.connolly@linaro.org wrote:
Hi Simon,
On 29/05/2024 18:30, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
Where is this tool used? Can you add a man page and test?
It currently doesn't have any users, it just allows for pre-generating GUIDs ahead of time for a given board. It might be hooked up to vendor tooling or some usecase like that.
I've somehow escaped learning Groff before now heh, I can give it a go and write a test.
OK (just copy bits of mkimage.1 :-)
Re the test, would this be something that binman would use? If so you could put a test there.
Regards, Simon

Hi Simon,
On 29/05/2024 21:45, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 13:02, Caleb Connolly caleb.connolly@linaro.org wrote:
Hi Simon,
On 29/05/2024 18:30, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
Where is this tool used? Can you add a man page and test?
It currently doesn't have any users, it just allows for pre-generating GUIDs ahead of time for a given board. It might be hooked up to vendor tooling or some usecase like that.
I've somehow escaped learning Groff before now heh, I can give it a go and write a test.
OK (just copy bits of mkimage.1 :-)
Right :D
Re the test, would this be something that binman would use? If so you could put a test there.
This tool is just a wrapper for gen_uuid_v5() which we already test in the dynamic UUID unit tests also introduced in this series. I don't think it's necessary to additionally test this tool.
Kind regards,
Regards, Simon

On 29.05.2024 16:48, Caleb Connolly wrote:
Add a tool that can generate GUIDs that match those generated internally by U-Boot for capsule update fw_images.
Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it with the board model, compatible, and fw_image name.
This tool accepts the same inputs and will produce the same GUID as U-Boot would at runtime.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org
tools/Makefile | 3 ++ tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+)
diff --git a/tools/Makefile b/tools/Makefile index 6a4280e3668f..7db7723793d5 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -253,8 +253,11 @@ HOSTLDLIBS_mkeficapsule += \ HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule
+genguid-objs := generated/lib/uuid.o generated/lib/sha1.o genguid.o +hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += genguid
mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
diff --git a/tools/genguid.c b/tools/genguid.c new file mode 100644 index 000000000000..dbac65d42623 --- /dev/null +++ b/tools/genguid.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright 2024 Linaro Ltd.
- Author: Caleb Connolly
- */
+#include <getopt.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <linux/types.h>
+#include <uuid.h>
+static struct option options[] = {
- {"dtb", required_argument, NULL, 'd'},
- {"compat", required_argument, NULL, 'c'},
- {"help", no_argument, NULL, 'h'},
- {"verbose", no_argument, NULL, 'v'},
- {"json", no_argument, NULL, 'j'},
- {NULL, 0, NULL, 0},
+};
+static void usage(const char *progname) +{
- fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n", progname);
- fprintf(stderr,
"Generate a v5 GUID for one of more U-Boot fw_images the same way U-Boot does at runtime.\n");
- fprintf(stderr,
"\nOptions:\n"
" GUID namespace/salt GUID in 8-4-4-4-12 format\n"
" -h, --help display this help and exit\n"
" -c, --compat=COMPAT first compatible property in the board devicetree\n"
" -v, --verbose print debug messages\n"
" -j, --json output in JSON format\n"
" NAME... one or more names of fw_images to generate GUIDs for\n"
- );
- fprintf(stderr, "\nExample:\n");
- fprintf(stderr, " %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\n"
"\t-c \"qcom,qrb4210-rb2\" \\\n"
"\tQUALCOMM-UBOOT\n", progname);
+}
+size_t u16_strsize(const uint16_t *in) +{
- size_t i = 0, count = UINT16_MAX;
- while (count-- && in[i])
i++;
- return (i + 1) * sizeof(uint16_t);
+}
Hi Caleb,
Consistency-wise, unless you wanted this function (`u16_strsize`) non-static, it would be more consistent to make it static, like the `usage` function.
Otherwise LGTM.
+int main(int argc, char **argv) +{
- struct uuid namespace;
- char *namespace_str;
- char uuid_str[37];
- char **image_uuids;
- char *compatible = NULL;
- uint16_t **images_u16;
- char **images;
- int c, n_images;
- bool debug = false, json = false;
- if (argc < 2) {
usage(argv[0]);
return 1;
- }
- namespace_str = argv[1];
- /* The first arg is the GUID so skip it */
- while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) != -1) {
switch (c) {
case 'c':
compatible = strdup(optarg);
break;
case 'h':
usage(argv[0]);
return 0;
case 'v':
debug = true;
break;
case 'j':
json = true;
break;
default:
usage(argv[0]);
return 1;
}
- }
- if (!compatible) {
fprintf(stderr, "ERROR: Please specify the compatible property.\n\n");
usage(argv[0]);
return 1;
- }
- if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace, UUID_STR_FORMAT_GUID)) {
fprintf(stderr, "ERROR: Check that your UUID is formatted correctly.\n");
exit(EXIT_FAILURE);
- }
- /* This is probably not the best way to convert a string to a "u16" string */
- n_images = argc - optind - 1;
- images = argv + optind + 1;
- images_u16 = calloc(n_images, sizeof(char *));
- for (int i = 0; i < n_images; i++) {
images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2);
for (int j = 0; j < strlen(images[i]); j++)
images_u16[i][j] = (uint16_t)images[i][j];
- }
- if (debug) {
fprintf(stderr, "GUID: ");
uuid_bin_to_str((uint8_t *)&namespace, uuid_str, UUID_STR_FORMAT_GUID);
fprintf(stderr, "%s\n", uuid_str);
fprintf(stderr, "Compatible: \"%s\"\n", compatible);
fprintf(stderr, "Images: ");
for (int i = 0; i < n_images; i++)
fprintf(stderr, "\"%s\"%s", argv[optind + i + 1],
i == n_images - 1 ? "\n" : ", ");
- }
- image_uuids = calloc(n_images, sizeof(char *));
- for (int i = 0; i < n_images; i++) {
struct uuid image_type_id;
gen_uuid_v5(&namespace, &image_type_id,
compatible, strlen(compatible),
images_u16[i], u16_strsize(images_u16[i]) - sizeof(uint16_t),
NULL);
uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str, UUID_STR_FORMAT_GUID);
image_uuids[i] = strdup(uuid_str);
- }
- if (json) {
printf("[\n");
for (int i = 0; i < n_images; i++)
printf("\t{\"name\": \"%s\", \"uuid\": \"%s\"}%s\n", images[i], image_uuids[i],
i == n_images - 1 ? "" : ",");
printf("]\n");
- } else {
for (int i = 0; i < n_images; i++)
printf("%-24s| %s\n", images[i], image_uuids[i]);
- }
- return 0;
+}
-- 2.45.0
Best wishes, -- Dom Rodriguez GPG Fingerprint: EB0D 45E6 D0DC 1BA1 A2B5 FC24 72DC F123 1E54 BD43

Add some basic unit tests to validate that the UUID generation behaves as expected. This matches the implementation in efi_loader for sandbox and a Qualcomm board and should catch any regressions.
Signed-off-by: Caleb Connolly caleb.connolly@linaro.org --- test/lib/uuid.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+)
diff --git a/test/lib/uuid.c b/test/lib/uuid.c index 0914f2c47e77..843678ec7f00 100644 --- a/test/lib/uuid.c +++ b/test/lib/uuid.c @@ -7,15 +7,20 @@ * Authors: * Abdellatif El Khlifi abdellatif.elkhlifi@arm.com */
+#include <charset.h> #include <uuid.h> #include <test/lib.h> #include <test/test.h> #include <test/ut.h>
/* test UUID */ #define TEST_SVC_UUID "ed32d533-4209-99e6-2d72-cdd998a79cc0" +/* Sandbox namespace UUID */ +#define SANDBOX_NAMESPACE_UUID "09d7cf52-0720-4710-91d1-08469b7fe9c8" +/* Qcom namespace UUID */ +#define QCOM_NAMESPACE_UUID "2a5aa852-b856-4d97-baa9-5c5f4421551f"
#define UUID_SIZE 16
/* The UUID binary data (little-endian format) */ @@ -37,4 +42,89 @@ static int lib_test_uuid_to_le(struct unit_test_state *uts) return 0; }
LIB_TEST(lib_test_uuid_to_le, 0); + +#if CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) +struct dynamic_uuid_test_data { + const char *namespace; + const char *compatible; + const u16 *images[4]; + const char *expected_uuids[4]; +}; + +static int lib_test_dynamic_uuid_case(struct unit_test_state *uts, + const struct dynamic_uuid_test_data *data) +{ + struct uuid namespace; + int j; + + ut_assertok(uuid_str_to_bin(data->namespace, (unsigned char *)&namespace, + UUID_STR_FORMAT_GUID)); + + for (j = 0; data->images[j]; j++) { + const char *expected_uuid = data->expected_uuids[j]; + const u16 *image = data->images[j]; + struct uuid uuid; + char uuid_str[37]; + + gen_uuid_v5(&namespace, &uuid, + data->compatible, strlen(data->compatible), + image, u16_strsize(image) - sizeof(uint16_t), + NULL); + uuid_bin_to_str((unsigned char *)&uuid, uuid_str, UUID_STR_FORMAT_GUID); + + ut_asserteq_str(expected_uuid, uuid_str); + } + + return 0; +} + +static int lib_test_dynamic_uuid(struct unit_test_state *uts) +{ + int ret, i; + + ut_unsilence_console(uts); + static const struct dynamic_uuid_test_data test_data[] = { + { + .compatible = "sandbox", + .namespace = SANDBOX_NAMESPACE_UUID, + .images = { + u"SANDBOX-UBOOT", + u"SANDBOX-UBOOT-ENV", + u"SANDBOX-FIT", + NULL, + }, + .expected_uuids = { + "fd5db83c-12f3-a46b-80a9-e3007c7ff56e", + "935fe837-fac8-4394-c008-737d8852c60d", + "ffd97379-0956-fa94-c003-8bfcf5cc097b", + NULL, + } + }, + { + .compatible = "qcom,qrb4210-rb2", + .namespace = QCOM_NAMESPACE_UUID, + .images = { + u"QUALCOMM-UBOOT", + NULL, + }, + .expected_uuids = { + "8ee418dc-7e00-e156-80a7-274fbbc05ba8", + NULL, + } + }, + }; + + for (i = 0; i < ARRAY_SIZE(test_data); i++) { + ret = lib_test_dynamic_uuid_case(uts, &test_data[i]); + if (ret) + return ret; + } + + return 0; +} + +LIB_TEST(lib_test_dynamic_uuid, 0); + +#endif /* CONFIG_IS_ENABLED(EFI_CAPSULE_DYNAMIC_UUIDS) */ +

[...]
+static int lib_test_dynamic_uuid(struct unit_test_state *uts) +{
int ret, i;
ut_unsilence_console(uts);
static const struct dynamic_uuid_test_data test_data[] = {
You pass this a function ptr, I guess the static is a typo?
{
.compatible = "sandbox",
.namespace = SANDBOX_NAMESPACE_UUID,
.images = {
u"SANDBOX-UBOOT",
u"SANDBOX-UBOOT-ENV",
u"SANDBOX-FIT",
NULL,
},
[...]
Cheers /Ilias

Hi Caleb,
On Wed, 29 May 2024 at 08:49, Caleb Connolly caleb.connolly@linaro.org wrote:
As more boards adopt support for the EFI CapsuleUpdate mechanism, there is a growing issue of being able to target updates to them properly. The current mechanism of hardcoding UUIDs for each board at compile time is unsustainable, and maintaining lists of GUIDs is similarly cumbersome.
In this series, I propose that we adopt v5 GUIDs, these are generated by using a well-known salt GUID as well as board specific information (like the model/revision), these are hashed together and the result is truncated to form a new UUID.
The well-known salt GUID can be specific to the architecture (SoC vendor), or OEM. It is defined in the board defconfig so that vendors can easily bring their own.
Specifically, the following fields are used to generate a GUID for a particular fw_image:
- namespace salt
- board compatible (usually the first entry in the dt root compatible array).
- fw_image name (the string identifying the specific image, especially relevant for board that can update multiple images).
== Usage ==
Boards can integrate dynamic UUID support as follows:
- Adjust Kconfig to depend on EFI_CAPSULE_DYNAMIC_UUIDS if EFI_HAVE_CAPSULE_SUPPORT.
- Skip setting the fw_images image_type_id property.
- Generate a UUID and set CONFIG_EFI_CAPSULE_NAMESPACE_UUID in your defconfig.
== Limitations ==
- Changing GUIDs
The primary limitation with this approach is that if any of the source fields change, so will the GUID for the board. It is therefore pretty important to ensure that GUID changes are caught during development.
- Supporting multiple boards with a single image
This now requires having an entry with the GUID for every board which might lead to larger UpdateCapsule images.
== Tooling ==
This series introduces a new tool: genguid. This can be used to generate the same GUIDs that the board would at runtime.
This series follows a related discussion started by Ilias: https://lore.kernel.org/u-boot/CAC_iWjJNHa4gMF897MqYZNdbgjFG8K4kwGsTXWuy72Wk...
To: Tom Rini trini@konsulko.com To: Heinrich Schuchardt xypron.glpk@gmx.de To: Ilias Apalodimas ilias.apalodimas@linaro.org To: Simon Glass sjg@chromium.org To: Mario Six mario.six@gdsys.cc To: Alper Nebi Yasak alpernebiyasak@gmail.com To: Abdellatif El Khlifi abdellatif.elkhlifi@arm.com Cc: Richard Hughes hughsient@gmail.com Cc: u-boot@lists.denx.de
Changes in v2:
- Move namespace UUID to be defined in defconfig
- Add tests and tooling
- Only use the first board compatible to generate UUID.
- Link to v1: https://lore.kernel.org/r/20240426-b4-dynamic-uuid-v1-0-e8154e00ec44@linaro....
Caleb Connolly (7): lib: uuid: add UUID v5 support efi: add a helper to generate dynamic UUIDs doc: uefi: document dynamic UUID generation sandbox: switch to dynamic UUIDs lib: uuid: supporting building as part of host tools tools: add genguid tool test: lib/uuid: add unit tests for dynamic UUIDs
arch/Kconfig | 1 + board/sandbox/sandbox.c | 16 --- configs/sandbox_defconfig | 1 + configs/sandbox_flattree_defconfig | 1 + doc/develop/uefi/uefi.rst | 31 +++++ include/sandbox_efi_capsule.h | 6 +- include/uuid.h | 21 ++- lib/Kconfig | 8 ++ lib/efi_loader/Kconfig | 23 +++ lib/efi_loader/efi_capsule.c | 1 + lib/efi_loader/efi_firmware.c | 66 +++++++++ lib/uuid.c | 81 +++++++++-- test/lib/uuid.c | 90 ++++++++++++ .../test_efi_capsule/test_capsule_firmware_fit.py | 2 +- .../test_efi_capsule/test_capsule_firmware_raw.py | 8 +- .../test_capsule_firmware_signed_fit.py | 2 +- .../test_capsule_firmware_signed_raw.py | 4 +- test/py/tests/test_efi_capsule/version.dts | 6 +- tools/Makefile | 3 + tools/binman/etype/efi_capsule.py | 2 +- tools/binman/ftest.py | 2 +- tools/genguid.c | 154 +++++++++++++++++++++ 22 files changed, 481 insertions(+), 48 deletions(-)
change-id: 20240422-b4-dynamic-uuid-1a5ab1486c27 base-commit: 2e682a4a406fc81ef32e05c28542cc8067f1e15f
How about using the compatible string, instead of a GUID? Is that possible?
Regards, Simon

Hi Caleb,
On Wed, 29 May 2024 at 13:04, Caleb Connolly caleb.connolly@linaro.org wrote:
How about using the compatible string, instead of a GUID? Is that possible?
Could you elaborate?
Well, the compatible string (in the root node) is how we normally decide which DT to use and which machine we are targeting. So I am wondering if we can somehow use that, rather than producing a GUID from it?
Regards, Simon

On 29/05/2024 21:49, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 13:04, Caleb Connolly caleb.connolly@linaro.org wrote:
How about using the compatible string, instead of a GUID? Is that possible?
Could you elaborate?
Well, the compatible string (in the root node) is how we normally decide which DT to use and which machine we are targeting. So I am wondering if we can somehow use that, rather than producing a GUID from it?
No, we can't. The GUIDs are required as part of the EFI FMP spec. We currently hardcode these GUIDs per-board (meaning if a vendor productize some board they need to make source modifications to replace the GUIDs). This series generates those GUIDs deterministically at runtime so we don't need to maintain a big list.
Regards, Simon

Hi Caleb,
On Wed, 29 May 2024 at 15:55, Caleb Connolly caleb.connolly@linaro.org wrote:
On 29/05/2024 21:49, Simon Glass wrote:
Hi Caleb,
On Wed, 29 May 2024 at 13:04, Caleb Connolly caleb.connolly@linaro.org wrote:
How about using the compatible string, instead of a GUID? Is that possible?
Could you elaborate?
Well, the compatible string (in the root node) is how we normally decide which DT to use and which machine we are targeting. So I am wondering if we can somehow use that, rather than producing a GUID from it?
No, we can't. The GUIDs are required as part of the EFI FMP spec. We currently hardcode these GUIDs per-board (meaning if a vendor productize some board they need to make source modifications to replace the GUIDs). This series generates those GUIDs deterministically at runtime so we don't need to maintain a big list.
OK thanks for the info.
Regards, Simon
participants (4)
-
Caleb Connolly
-
Dom (shymega) Rodriguez
-
Ilias Apalodimas
-
Simon Glass