[2/6] efi_loader: Add device path related functions for initrd via Boot####

Message ID 20210313214738.3257922-3-ilias.apalodimas@linaro.org
State Superseded
Headers show
Series
  • Loadfile2 for initrd loading
Related show

Commit Message

Ilias Apalodimas March 13, 2021, 9:47 p.m.
On the following patches we allow for an initrd path to be stored in
Boot#### variables.  Specifically we encode in the FIlePathList[] of
the EFI_LOAD_OPTIONS for each Boot#### variable.

The FilePathList[] array looks like this:
kernel - 0xff - VenMedia(initrd GUID) - initrd1 - 0x01 initrd2 - 0xff
So let's add the relevant functions to concatenate and retrieve a device
path based on a Vendor GUID.

Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>

---
 include/efi_loader.h             |  4 ++
 lib/efi_loader/efi_device_path.c | 99 ++++++++++++++++++++++++++++++--
 2 files changed, 98 insertions(+), 5 deletions(-)

-- 
2.30.1

Comments

Heinrich Schuchardt March 14, 2021, 7:19 a.m. | #1
On 3/13/21 10:47 PM, Ilias Apalodimas wrote:
> On the following patches we allow for an initrd path to be stored in

> Boot#### variables.  Specifically we encode in the FIlePathList[] of

> the EFI_LOAD_OPTIONS for each Boot#### variable.

>

> The FilePathList[] array looks like this:

> kernel - 0xff - VenMedia(initrd GUID) - initrd1 - 0x01 initrd2 - 0xff

> So let's add the relevant functions to concatenate and retrieve a device

> path based on a Vendor GUID.

>

> Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>

> ---

>   include/efi_loader.h             |  4 ++

>   lib/efi_loader/efi_device_path.c | 99 ++++++++++++++++++++++++++++++--

>   2 files changed, 98 insertions(+), 5 deletions(-)

>

> diff --git a/include/efi_loader.h b/include/efi_loader.h

> index f470bbd636f4..eb11a8c7d4b1 100644

> --- a/include/efi_loader.h

> +++ b/include/efi_loader.h

> @@ -738,6 +738,10 @@ struct efi_load_option {

>   	const u8 *optional_data;

>   };

>

> +struct efi_device_path *efi_dp_from_lo(struct efi_load_option *lo,

> +				       efi_uintn_t *size, efi_guid_t guid);

> +struct efi_device_path *efi_dp_concat(const struct efi_device_path *dp1,

> +				      const struct efi_device_path *dp2);

>   efi_status_t efi_deserialize_load_option(struct efi_load_option *lo, u8 *data,

>   					 efi_uintn_t *size);

>   unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data);

> diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c

> index c9315dd45857..1f55c772dc83 100644

> --- a/lib/efi_loader/efi_device_path.c

> +++ b/lib/efi_loader/efi_device_path.c

> @@ -282,11 +282,25 @@ struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp)

>   	return ndp;

>   }

>

> -struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,

> -				      const struct efi_device_path *dp2)

> +/** _efi_dp_append() - Append or concatenate two device paths.


Please, add an empty line.

> + *                     Concatenated device path will be separated by a 0xff end


... by a sub-type 0xff end node.

> + *                     node.

> + *

> + * @dp1:	First device path

> + * @dp2:	Second device path


Please, describe argument @concat.

> + *

> + * Return:	concatenated device path or NULL. Caller must free the returned

> + *              value

> + */

> +static struct efi_device_path *_efi_dp_append(const struct efi_device_path *dp1,


This function name is very close to efi_dp_append. Maybe
efi_dp_append_or_concatenate() would be a better function name.

> +					      const struct efi_device_path *dp2,

> +					      bool concat)

>   {

>   	struct efi_device_path *ret;

> +	size_t end_size = sizeof(END);

>

> +	if (concat)

> +		end_size = 2 * sizeof(END);

>   	if (!dp1 && !dp2) {

>   		/* return an end node */

>   		ret = efi_dp_dup(&END);

> @@ -298,18 +312,56 @@ struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,

>   		/* both dp1 and dp2 are non-null */

>   		unsigned sz1 = efi_dp_size(dp1);

>   		unsigned sz2 = efi_dp_size(dp2);

> -		void *p = dp_alloc(sz1 + sz2 + sizeof(END));

> +		void *p = dp_alloc(sz1 + sz2 + end_size);

>   		if (!p)

>   			return NULL;

> +		ret = p;

>   		memcpy(p, dp1, sz1);

> +		p += sz1;

> +

> +		if (concat) {

> +			memcpy(p, &END, sizeof(END));

> +			p += sizeof(END);

> +		}

> +

>   		/* the end node of the second device path has to be retained */

> -		memcpy(p + sz1, dp2, sz2 + sizeof(END));

> -		ret = p;

> +		memcpy(p, dp2, sz2);

> +		p += sz2;

> +		memcpy(p, &END, sizeof(END));

>   	}

>

>   	return ret;

>   }

>

> +/** efi_dp_append() - Append a device to an existing device path.

> + *

> + * @dp1:	First device path

> + * @dp2:	Second device path

> + *

> + * Return:	concatenated device path or NULL. Caller must free the returned

> + *              value

> + */

> +struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,

> +				      const struct efi_device_path *dp2)

> +{

> +	return _efi_dp_append(dp1, dp2, false);

> +}

> +

> +/** efi_dp_concat() - Concatenate 2 device paths. The final device path will

> + *                    contain two device paths separated by and end node (0xff).

> + *

> + * @dp1:	First device path

> + * @dp2:	Second device path

> + *

> + * Return:	concatenated device path or NULL. Caller must free the returned

> + *              value

> + */

> +struct efi_device_path *efi_dp_concat(const struct efi_device_path *dp1,

> +				      const struct efi_device_path *dp2)

> +{

> +	return _efi_dp_append(dp1, dp2, true);

> +}

> +

>   struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp,

>   					   const struct efi_device_path *node)

>   {

> @@ -1160,3 +1212,40 @@ ssize_t efi_dp_check_length(const struct efi_device_path *dp,

>   		dp = (const struct efi_device_path *)((const u8 *)dp + len);

>   	}

>   }

> +

> +/**

> + * efi_dp_from_lo() - Get the instance of a VenMedia node in a

> + *                    multi-instance device path that matches

> + *                    a specific GUID. This kind of device paths

> + *                    is found in Boot#### options describing an

> + *                    initrd location

> + *

> + * @load_option: EFI_LOAD_OPTION containing a valid device path

> + * @size:	 size of the discovered device path

> + * @guid:	 guid to search for

> + *

> + * Return:	 device path including the VenMedia node or NULL.

> + *               Caller must free the returned value

> + */

> +struct

> +efi_device_path *efi_dp_from_lo(struct efi_load_option *lo,

> +				efi_uintn_t *size, efi_guid_t guid)

> +{

> +	struct efi_device_path *fp = lo->file_path;

> +	struct efi_device_path_vendor *vendor;

> +	int lo_len = lo->file_path_length;

> +

> +	for (; lo_len >=  sizeof(struct efi_device_path);

> +	     lo_len -= fp->length, fp = (void *)fp + fp->length) {

> +		if (fp->type != DEVICE_PATH_TYPE_MEDIA_DEVICE ||

> +		    fp->sub_type != DEVICE_PATH_SUB_TYPE_VENDOR_PATH)

> +			continue;


The device path is provided by the user and may be constructed incorrectly.

lo_len might be negative here. Or the remaining device path might not
fit into lo_len.

Function efi_dp_check_length() can be used to check the size but it
currently accepts only positive values of maxlen. Maybe we should change
the type of maxlen to ssize() in that function.

Best regards

Heinrich

> +

> +		vendor = (struct efi_device_path_vendor *)fp;

> +		if (!guidcmp(&vendor->guid, &guid))

> +			return efi_dp_dup(fp);

> +	}

> +	log_debug("VenMedia(%pUl) not found in %ls\n", &guid, lo->label);

> +

> +	return NULL;

> +}

>
Ilias Apalodimas March 14, 2021, 7:32 a.m. | #2
On Sun, Mar 14, 2021 at 08:19:49AM +0100, Heinrich Schuchardt wrote:
> > + *               Caller must free the returned value


[...]

> > + */

> > +struct

> > +efi_device_path *efi_dp_from_lo(struct efi_load_option *lo,

> > +				efi_uintn_t *size, efi_guid_t guid)

> > +{

> > +	struct efi_device_path *fp = lo->file_path;

> > +	struct efi_device_path_vendor *vendor;

> > +	int lo_len = lo->file_path_length;

> > +

> > +	for (; lo_len >=  sizeof(struct efi_device_path);

> > +	     lo_len -= fp->length, fp = (void *)fp + fp->length) {

> > +		if (fp->type != DEVICE_PATH_TYPE_MEDIA_DEVICE ||

> > +		    fp->sub_type != DEVICE_PATH_SUB_TYPE_VENDOR_PATH)

> > +			continue;

> 

> The device path is provided by the user and may be constructed incorrectly.

> 

> lo_len might be negative here. Or the remaining device path might not

> fit into lo_len.

> 

> Function efi_dp_check_length() can be used to check the size but it

> currently accepts only positive values of maxlen. Maybe we should change

> the type of maxlen to ssize() in that function.

> 


Yea, I forgot to fix this one. 

Regards
/Ilias
> Best regards

> 

> Heinrich

> 

> > +

> > +		vendor = (struct efi_device_path_vendor *)fp;

> > +		if (!guidcmp(&vendor->guid, &guid))

> > +			return efi_dp_dup(fp);

> > +	}

> > +	log_debug("VenMedia(%pUl) not found in %ls\n", &guid, lo->label);

> > +

> > +	return NULL;

> > +}

> > 

>

Patch

diff --git a/include/efi_loader.h b/include/efi_loader.h
index f470bbd636f4..eb11a8c7d4b1 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -738,6 +738,10 @@  struct efi_load_option {
 	const u8 *optional_data;
 };
 
+struct efi_device_path *efi_dp_from_lo(struct efi_load_option *lo,
+				       efi_uintn_t *size, efi_guid_t guid);
+struct efi_device_path *efi_dp_concat(const struct efi_device_path *dp1,
+				      const struct efi_device_path *dp2);
 efi_status_t efi_deserialize_load_option(struct efi_load_option *lo, u8 *data,
 					 efi_uintn_t *size);
 unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data);
diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c
index c9315dd45857..1f55c772dc83 100644
--- a/lib/efi_loader/efi_device_path.c
+++ b/lib/efi_loader/efi_device_path.c
@@ -282,11 +282,25 @@  struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp)
 	return ndp;
 }
 
-struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,
-				      const struct efi_device_path *dp2)
+/** _efi_dp_append() - Append or concatenate two device paths.
+ *                     Concatenated device path will be separated by a 0xff end
+ *                     node.
+ *
+ * @dp1:	First device path
+ * @dp2:	Second device path
+ *
+ * Return:	concatenated device path or NULL. Caller must free the returned
+ *              value
+ */
+static struct efi_device_path *_efi_dp_append(const struct efi_device_path *dp1,
+					      const struct efi_device_path *dp2,
+					      bool concat)
 {
 	struct efi_device_path *ret;
+	size_t end_size = sizeof(END);
 
+	if (concat)
+		end_size = 2 * sizeof(END);
 	if (!dp1 && !dp2) {
 		/* return an end node */
 		ret = efi_dp_dup(&END);
@@ -298,18 +312,56 @@  struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,
 		/* both dp1 and dp2 are non-null */
 		unsigned sz1 = efi_dp_size(dp1);
 		unsigned sz2 = efi_dp_size(dp2);
-		void *p = dp_alloc(sz1 + sz2 + sizeof(END));
+		void *p = dp_alloc(sz1 + sz2 + end_size);
 		if (!p)
 			return NULL;
+		ret = p;
 		memcpy(p, dp1, sz1);
+		p += sz1;
+
+		if (concat) {
+			memcpy(p, &END, sizeof(END));
+			p += sizeof(END);
+		}
+
 		/* the end node of the second device path has to be retained */
-		memcpy(p + sz1, dp2, sz2 + sizeof(END));
-		ret = p;
+		memcpy(p, dp2, sz2);
+		p += sz2;
+		memcpy(p, &END, sizeof(END));
 	}
 
 	return ret;
 }
 
+/** efi_dp_append() - Append a device to an existing device path.
+ *
+ * @dp1:	First device path
+ * @dp2:	Second device path
+ *
+ * Return:	concatenated device path or NULL. Caller must free the returned
+ *              value
+ */
+struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1,
+				      const struct efi_device_path *dp2)
+{
+	return _efi_dp_append(dp1, dp2, false);
+}
+
+/** efi_dp_concat() - Concatenate 2 device paths. The final device path will
+ *                    contain two device paths separated by and end node (0xff).
+ *
+ * @dp1:	First device path
+ * @dp2:	Second device path
+ *
+ * Return:	concatenated device path or NULL. Caller must free the returned
+ *              value
+ */
+struct efi_device_path *efi_dp_concat(const struct efi_device_path *dp1,
+				      const struct efi_device_path *dp2)
+{
+	return _efi_dp_append(dp1, dp2, true);
+}
+
 struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp,
 					   const struct efi_device_path *node)
 {
@@ -1160,3 +1212,40 @@  ssize_t efi_dp_check_length(const struct efi_device_path *dp,
 		dp = (const struct efi_device_path *)((const u8 *)dp + len);
 	}
 }
+
+/**
+ * efi_dp_from_lo() - Get the instance of a VenMedia node in a
+ *                    multi-instance device path that matches
+ *                    a specific GUID. This kind of device paths
+ *                    is found in Boot#### options describing an
+ *                    initrd location
+ *
+ * @load_option: EFI_LOAD_OPTION containing a valid device path
+ * @size:	 size of the discovered device path
+ * @guid:	 guid to search for
+ *
+ * Return:	 device path including the VenMedia node or NULL.
+ *               Caller must free the returned value
+ */
+struct
+efi_device_path *efi_dp_from_lo(struct efi_load_option *lo,
+				efi_uintn_t *size, efi_guid_t guid)
+{
+	struct efi_device_path *fp = lo->file_path;
+	struct efi_device_path_vendor *vendor;
+	int lo_len = lo->file_path_length;
+
+	for (; lo_len >=  sizeof(struct efi_device_path);
+	     lo_len -= fp->length, fp = (void *)fp + fp->length) {
+		if (fp->type != DEVICE_PATH_TYPE_MEDIA_DEVICE ||
+		    fp->sub_type != DEVICE_PATH_SUB_TYPE_VENDOR_PATH)
+			continue;
+
+		vendor = (struct efi_device_path_vendor *)fp;
+		if (!guidcmp(&vendor->guid, &guid))
+			return efi_dp_dup(fp);
+	}
+	log_debug("VenMedia(%pUl) not found in %ls\n", &guid, lo->label);
+
+	return NULL;
+}