[PATCH] efi_loader: Trigger capsule updates with automatically generated boot options

Ilias Apalodimas ilias.apalodimas at linaro.org
Tue Dec 2 09:50:58 CET 2025


The EFI spec in §8.5.5 says
"The directory \EFI\UpdateCapsule is checked for capsules only within
 the EFI system partition on the device specified in the active boot
 option determine by reference to BootNext variable or BootOrder variable
 processing."

Automatically generated boot options don't point to the ESP, they point to
the disk itself and find_handle() won't match when searching for an ESP
during a capsule update.
This happens because find_handle() only matches device paths that are
shorter or equal to the device path passed as an argument.
Since the EFI spec allows it we want to allow capsule updates, when the
boot option points to a disk, but that disk contains an ESP with a
\EFI\UpdateCapsule  directory.

find_handle() implicitly changes it's behavior depending on the passed
arguments as well. If the 'rem' ptr is not valid it will only search for
an exact match. Otherwise it will match on the best match and return the
remaining portion of the matched device path to 'rem'.

Tweak this slightly and pass an enum with 3 options
- EXACT   - Exact match, as if 'rem' was NULL
- SHORTER - Match a shorter device path and put the remainder in 'rem'
- LONGER  - Inverse the logic and match a longer device path. Put the
            remainder into 'rem'.

To avoid any regressions, LONGER is only used during capsule updates and
since 'rem' always carries the remainder of the matched device path we
can plug it in with minimal changes.

Reported-by: Balaji Selvanathan <balaji.selvanathan at oss.qualcomm.com>
Reported-by: John Toomey <john.toomey at amd.com>
Signed-off-by: Ilias Apalodimas <ilias.apalodimas at linaro.org>
---

John, Balaji, can you check if this fixes your problem?

 cmd/eficonfig.c                  |  2 +-
 include/efi_device_path.h        | 10 +++---
 lib/efi_loader/efi_bootmgr.c     |  6 ++--
 lib/efi_loader/efi_boottime.c    |  5 +--
 lib/efi_loader/efi_capsule.c     | 20 +++++++-----
 lib/efi_loader/efi_device_path.c | 55 +++++++++++++++++++++++++-------
 lib/efi_loader/efi_disk.c        |  2 +-
 7 files changed, 70 insertions(+), 30 deletions(-)

diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c
index d8d946c87ac8..e317f3c90c14 100644
--- a/cmd/eficonfig.c
+++ b/cmd/eficonfig.c
@@ -1321,7 +1321,7 @@ static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char
 		return EFI_OUT_OF_RESOURCES;

 	/* get the device name only when the user already selected the file path */
-	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL);
+	handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL, false);
 	if (handle) {
 		ret = efi_disk_get_device_name(handle, devname, EFICONFIG_VOLUME_PATH_MAX);
 		if (ret != EFI_SUCCESS)
diff --git a/include/efi_device_path.h b/include/efi_device_path.h
index aae85228f681..f9c538730c94 100644
--- a/include/efi_device_path.h
+++ b/include/efi_device_path.h
@@ -73,13 +73,15 @@ struct efi_device_path *efi_dp_shorten(struct efi_device_path *dp);
  *
  * If @rem is provided, the handle with the longest partial match is returned.
  *
- * @dp:     device path to search
- * @guid:   GUID of protocol that must be installed on path or NULL
- * @rem:    pointer to receive remaining device path
+ * @dp:			device path to search
+ * @guid:		GUID of protocol that must be installed on path or NULL
+ * @rem:		pointer to receive remaining device path
+ * @inversed_match:	Match even if the dp is shorter. rem will stil hoild the
+ *                      remaining device path
  * Return:  matching handle
  */
 efi_handle_t efi_dp_find_obj(struct efi_device_path *dp, const efi_guid_t *guid,
-			     struct efi_device_path **rem);
+			     struct efi_device_path **rem, bool inversed_match);

 /**
  * efi_dp_last_node() - Determine the last device path node before the end node
diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c
index a687f4d8e85c..df7500a94876 100644
--- a/lib/efi_loader/efi_bootmgr.c
+++ b/lib/efi_loader/efi_bootmgr.c
@@ -84,7 +84,8 @@ struct efi_device_path *expand_media_path(struct efi_device_path *device_path)
 	 * booting from removable media.
 	 */
 	handle = efi_dp_find_obj(device_path,
-				 &efi_simple_file_system_protocol_guid, &rem);
+				 &efi_simple_file_system_protocol_guid, &rem,
+				 false);
 	if (handle) {
 		if (rem->type == DEVICE_PATH_TYPE_END) {
 			char fname[30];
@@ -625,7 +626,8 @@ static efi_status_t try_load_from_media(struct efi_device_path *file_path,
 	struct efi_device_path *rem, *dp = NULL;
 	struct efi_device_path *final_dp = file_path;

-	handle_blkdev = efi_dp_find_obj(file_path, &efi_block_io_guid, &rem);
+	handle_blkdev = efi_dp_find_obj(file_path, &efi_block_io_guid, &rem,
+					false);
 	if (handle_blkdev) {
 		if (rem->type == DEVICE_PATH_TYPE_END) {
 			/* no file name present, try default file */
diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c
index ddc935d22409..0391b92a8be4 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -1816,7 +1816,8 @@ efi_status_t efi_setup_loaded_image(struct efi_device_path *device_path,
 	info->system_table = &systab;

 	if (device_path) {
-		info->device_handle = efi_dp_find_obj(device_path, NULL, NULL);
+		info->device_handle = efi_dp_find_obj(device_path, NULL, NULL,
+						      false);

 		dp = efi_dp_concat(device_path, file_path, 0);
 		if (!dp) {
@@ -2016,7 +2017,7 @@ efi_status_t efi_load_image_from_path(bool boot_policy,
 	*size = 0;

 	dp = file_path;
-	device = efi_dp_find_obj(dp, NULL, &rem);
+	device = efi_dp_find_obj(dp, NULL, &rem, false);
 	ret = efi_search_protocol(device, &efi_simple_file_system_protocol_guid,
 				  NULL);
 	if (ret == EFI_SUCCESS)
diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c
index eafc647f558f..103328c76a6e 100644
--- a/lib/efi_loader/efi_capsule.c
+++ b/lib/efi_loader/efi_capsule.c
@@ -886,18 +886,18 @@ static efi_status_t get_dp_device(u16 *boot_var,
  * @dp		device path
  * Return:	true - yes, false - no
  */
-static bool device_is_present_and_system_part(struct efi_device_path *dp)
+static bool device_is_present_and_system_part(struct efi_device_path *dp,
+					      struct efi_device_path **rem)
 {
 	efi_handle_t handle;
-	struct efi_device_path *rem;

 	/* Check device exists */
-	handle = efi_dp_find_obj(dp, NULL, NULL);
+	handle = efi_dp_find_obj(dp, NULL, NULL, false);
 	if (!handle)
 		return false;

 	/* Check device is on system partition */
-	handle = efi_dp_find_obj(dp, &efi_system_partition_guid, &rem);
+	handle = efi_dp_find_obj(dp, &efi_system_partition_guid, rem, true);
 	if (!handle)
 		return false;

@@ -919,7 +919,7 @@ static efi_status_t find_boot_device(void)
 	efi_uintn_t size;
 	int i, num;
 	struct efi_simple_file_system_protocol *volume;
-	struct efi_device_path *boot_dev = NULL;
+	struct efi_device_path *boot_dev = NULL, *rem = NULL;
 	efi_status_t ret;

 	/* find active boot device in BootNext */
@@ -940,7 +940,7 @@ static efi_status_t find_boot_device(void)

 		ret = get_dp_device(boot_var16, &boot_dev);
 		if (ret == EFI_SUCCESS) {
-			if (device_is_present_and_system_part(boot_dev)) {
+			if (device_is_present_and_system_part(boot_dev, &rem)) {
 				goto found;
 			} else {
 				efi_free_pool(boot_dev);
@@ -967,7 +967,7 @@ skip:
 		if (ret != EFI_SUCCESS)
 			continue;

-		if (device_is_present_and_system_part(boot_dev))
+		if (device_is_present_and_system_part(boot_dev, &rem))
 			break;

 		efi_free_pool(boot_dev);
@@ -977,7 +977,11 @@ found:
 	if (boot_dev) {
 		log_debug("Boot device %pD\n", boot_dev);

-		volume = efi_fs_from_path(boot_dev);
+		if (rem->type == DEVICE_PATH_TYPE_END)
+			volume = efi_fs_from_path(boot_dev);
+		else
+			volume = efi_fs_from_path(rem);
+
 		if (!volume)
 			ret = EFI_DEVICE_ERROR;
 		else
diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c
index b3fb20b2501e..ad7f452c411b 100644
--- a/lib/efi_loader/efi_device_path.c
+++ b/lib/efi_loader/efi_device_path.c
@@ -30,6 +30,12 @@ const struct efi_device_path EFI_DP_END = {
 	.length   = sizeof(EFI_DP_END),
 };

+enum match {
+	EXACT,
+	SHORTER,
+	LONGER
+};
+
 #if defined(CONFIG_MMC)
 /*
  * Determine if an MMC device is an SD card.
@@ -109,10 +115,11 @@ struct efi_device_path *efi_dp_shorten(struct efi_device_path *dp)
  */
 static efi_handle_t find_handle(struct efi_device_path *dp,
 				const efi_guid_t *guid, bool short_path,
-				struct efi_device_path **rem)
+				struct efi_device_path **rem,
+				enum match match)
 {
 	efi_handle_t handle, best_handle = NULL;
-	efi_uintn_t len, best_len = 0;
+	efi_uintn_t len, match_len = 0, best_len = 0;

 	len = efi_dp_instance_size(dp);

@@ -137,22 +144,35 @@ static efi_handle_t find_handle(struct efi_device_path *dp,
 			if (!dp_current)
 				continue;
 		}
-		len_current = efi_dp_instance_size(dp_current);
-		if (rem) {
+
+		match_len = len_current = efi_dp_instance_size(dp_current);
+		switch (match) {
+		case EXACT:
+			if (len_current != len)
+				continue;
+			break;
+		case SHORTER:
 			if (len_current > len)
 				continue;
-		} else {
-			if (len_current != len)
+			break;
+		case LONGER:
+			if (len_current < len)
 				continue;
+			match_len = len;
+			break;
 		}
-		if (memcmp(dp_current, dp, len_current))
+
+		if (memcmp(dp_current, dp, match_len))
 			continue;
 		if (!rem)
 			return handle;
 		if (len_current > best_len) {
 			best_len = len_current;
 			best_handle = handle;
-			*rem = (void*)((u8 *)dp + len_current);
+			if (len_current <= len)
+				*rem = (void *)((uintptr_t)dp + len_current);
+			else
+				*rem = (void *)((uintptr_t)dp_current + match_len);
 		}
 	}
 	return best_handle;
@@ -160,14 +180,25 @@ static efi_handle_t find_handle(struct efi_device_path *dp,

 efi_handle_t efi_dp_find_obj(struct efi_device_path *dp,
 			     const efi_guid_t *guid,
-			     struct efi_device_path **rem)
+			     struct efi_device_path **rem, bool inversed_match)
 {
 	efi_handle_t handle;

-	handle = find_handle(dp, guid, false, rem);
+	handle = find_handle(dp, guid, false, rem, EXACT);
 	if (!handle)
-		/* Match short form device path */
-		handle = find_handle(dp, guid, true, rem);
+		handle = find_handle(dp, guid, true, rem, EXACT);
+	if (handle)
+		return handle;
+
+	handle = find_handle(dp, guid, false, rem, SHORTER);
+	if (!handle)
+		handle = find_handle(dp, guid, true, rem, SHORTER);
+
+	if (inversed_match && !handle) {
+		handle = find_handle(dp, guid, false, rem, LONGER);
+		if (!handle)
+			handle = find_handle(dp, guid, true, rem, LONGER);
+	}

 	return handle;
 }
diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
index 130c4db9606f..0d3d7c187d72 100644
--- a/lib/efi_loader/efi_disk.c
+++ b/lib/efi_loader/efi_disk.c
@@ -339,7 +339,7 @@ efi_fs_from_path(struct efi_device_path *full_path)
 	efi_free_pool(file_path);

 	/* Get the EFI object for the partition */
-	efiobj = efi_dp_find_obj(device_path, NULL, NULL);
+	efiobj = efi_dp_find_obj(device_path, NULL, NULL, false);
 	efi_free_pool(device_path);
 	if (!efiobj)
 		return NULL;
--
2.51.0



More information about the U-Boot mailing list