efi_loader: fix efi_dp_find_obj()

efi_dp_find_obj() should not return any handle with a partially matching
device path but the handle with the maximum matching device path.

Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c
index ddd5f13..aeb5264 100644
--- a/lib/efi_loader/efi_device_path.c
+++ b/lib/efi_loader/efi_device_path.c
@@ -159,69 +159,81 @@
 	return dp;
 }
 
-static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path,
-				   struct efi_device_path **rem)
+/**
+ * find_handle() - find handle by device path
+ *
+ * If @rem is provided, the handle with the longest partial match is returned.
+ *
+ * @dp:		device path to search
+ * @short_path:	use short form device path for matching
+ * @rem:	pointer to receive remaining device path
+ * Return:	matching handle
+ */
+static efi_handle_t find_handle(struct efi_device_path *dp, bool short_path,
+			        struct efi_device_path **rem)
 {
-	struct efi_object *efiobj;
-	efi_uintn_t dp_size = efi_dp_instance_size(dp);
+	efi_handle_t handle, best_handle = NULL;
+	efi_uintn_t len, best_len = 0;
+
+	len = efi_dp_instance_size(dp);
 
-	list_for_each_entry(efiobj, &efi_obj_list, link) {
+	list_for_each_entry(handle, &efi_obj_list, link) {
 		struct efi_handler *handler;
-		struct efi_device_path *obj_dp;
+		struct efi_device_path *dp_current;
+		efi_uintn_t len_current;
 		efi_status_t ret;
 
-		ret = efi_search_protocol(efiobj,
-					  &efi_guid_device_path, &handler);
+		ret = efi_search_protocol(handle, &efi_guid_device_path,
+					  &handler);
 		if (ret != EFI_SUCCESS)
 			continue;
-		obj_dp = handler->protocol_interface;
-
-		do {
-			if (efi_dp_match(dp, obj_dp) == 0) {
-				if (rem) {
-					/*
-					 * Allow partial matches, but inform
-					 * the caller.
-					 */
-					*rem = ((void *)dp) +
-						efi_dp_instance_size(obj_dp);
-					return efiobj;
-				} else {
-					/* Only return on exact matches */
-					if (efi_dp_instance_size(obj_dp) ==
-					    dp_size)
-						return efiobj;
-				}
-			}
-
-			obj_dp = efi_dp_shorten(efi_dp_next(obj_dp));
-		} while (short_path && obj_dp);
+		dp_current = handler->protocol_interface;
+		if (short_path) {
+			dp_current = efi_dp_shorten(dp_current);
+			if (!dp_current)
+				continue;
+		}
+		len_current = efi_dp_instance_size(dp_current);
+		if (rem) {
+			if (len_current < len)
+				continue;
+		} else {
+			if (len_current != len)
+				continue;
+		}
+		if (memcmp(dp_current, dp, len))
+			continue;
+		if (!rem)
+			return handle;
+		if (len_current > best_len) {
+			best_len = len_current;
+			best_handle = handle;
+			*rem = (void*)((u8 *)dp + len_current);
+		}
 	}
-
-	return NULL;
+	return best_handle;
 }
 
-/*
- * Find an efiobj from device-path, if 'rem' is not NULL, returns the
- * remaining part of the device path after the matched object.
+/**
+ * efi_dp_find_obj() - find handle by device path
+ *
+ * If @rem is provided, the handle with the longest partial match is returned.
+ *
+ * @dp:		device path to search
+ * @rem:	pointer to receive remaining device path
+ * Return:	matching handle
  */
-struct efi_object *efi_dp_find_obj(struct efi_device_path *dp,
-				   struct efi_device_path **rem)
+efi_handle_t efi_dp_find_obj(struct efi_device_path *dp,
+			     struct efi_device_path **rem)
 {
-	struct efi_object *efiobj;
+	efi_handle_t handle;
 
-	/* Search for an exact match first */
-	efiobj = find_obj(dp, false, NULL);
-
-	/* Then for a fuzzy match */
-	if (!efiobj)
-		efiobj = find_obj(dp, false, rem);
-
-	/* And now for a fuzzy short match */
-	if (!efiobj)
-		efiobj = find_obj(dp, true, rem);
+	handle = find_handle(dp, false, rem);
+	if (!handle)
+		/* Match short form device path */
+		handle = find_handle(dp, true, rem);
 
-	return efiobj;
+	return handle;
 }
 
 /*