]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Add ability to extract device path from an EFI load option
authorMichael Brown <mcb30@ipxe.org>
Thu, 28 Aug 2025 14:34:32 +0000 (15:34 +0100)
committerMichael Brown <mcb30@ipxe.org>
Fri, 29 Aug 2025 11:34:17 +0000 (12:34 +0100)
An EFI boot option (stored in a BootXXXX variable) comprises an
EFI_LOAD_OPTION structure, which includes some undefined number of EFI
device paths.  (The structure is extremely messy and awkward to parse
in C, but that's par for the course with EFI.)

Add a function to extract the first device path from an EFI load
option, along with wrapper functions to read and extract the first
device path from an EFI boot variable.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/efi/efi_path.h
src/interface/efi/efi_path.c

index 57fce4028b8ce844af81e9cf7460c94620973809..a37d7b9d703d1ad0734f57f021882a82356242ed 100644 (file)
@@ -43,6 +43,7 @@ efi_path_prev ( EFI_DEVICE_PATH_PROTOCOL *path,
 extern EFI_DEVICE_PATH_PROTOCOL *
 efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path );
 extern size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path );
+extern int efi_path_check ( EFI_DEVICE_PATH_PROTOCOL *path, size_t max );
 extern void * efi_path_mac ( EFI_DEVICE_PATH_PROTOCOL *path );
 extern unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path );
 extern int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *uuid );
@@ -58,6 +59,10 @@ extern EFI_DEVICE_PATH_PROTOCOL * efi_fcp_path ( struct fcp_description *desc );
 extern EFI_DEVICE_PATH_PROTOCOL *
 efi_ib_srp_path ( struct ib_srp_device *ib_srp );
 extern EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func );
+extern EFI_DEVICE_PATH_PROTOCOL * efi_load_path ( EFI_LOAD_OPTION *load,
+                                                 size_t len );
+extern EFI_DEVICE_PATH_PROTOCOL * efi_boot_path ( unsigned int number );
+extern EFI_DEVICE_PATH_PROTOCOL * efi_current_boot_path ( void );
 
 extern EFI_DEVICE_PATH_PROTOCOL * efi_describe ( struct interface *interface );
 #define efi_describe_TYPE( object_type ) \
index ac3c049878652f795c1ada27af319e43ea885652..65ac3d6f527cec4cc6157d531c27870f70d6d3ac 100644 (file)
@@ -38,6 +38,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/dhcp.h>
 #include <ipxe/efi/efi.h>
 #include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_strings.h>
 #include <ipxe/efi/efi_path.h>
 
 /** @file
@@ -147,6 +148,33 @@ size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ) {
        return ( ( ( void * ) end ) - ( ( void * ) path ) );
 }
 
+/**
+ * Check that device path is well-formed
+ *
+ * @v path             Device path, or NULL
+ * @v max              Maximum device path length
+ * @ret rc             Return status code
+ */
+int efi_path_check ( EFI_DEVICE_PATH_PROTOCOL *path, size_t max ) {
+       EFI_DEVICE_PATH_PROTOCOL *next;
+       size_t remaining = max;
+       size_t len;
+
+       /* Check that path terminates within maximum length */
+       for ( ; ; path = next ) {
+               if ( remaining < sizeof ( *path ) )
+                       return -EINVAL;
+               next = efi_path_next ( path );
+               if ( ! next )
+                       break;
+               len = ( ( ( void * ) next ) - ( ( void * ) path ) );
+               if ( remaining < len )
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
 /**
  * Get MAC address from device path
  *
@@ -668,6 +696,177 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
        return path;
 }
 
+/**
+ * Get EFI device path from load option
+ *
+ * @v load             EFI load option
+ * @v len              Length of EFI load option
+ * @ret path           EFI device path, or NULL on error
+ *
+ * The caller is responsible for eventually calling free() on the
+ * allocated device path.
+ */
+EFI_DEVICE_PATH_PROTOCOL * efi_load_path ( EFI_LOAD_OPTION *load,
+                                          size_t len ) {
+       EFI_DEVICE_PATH_PROTOCOL *path;
+       EFI_DEVICE_PATH_PROTOCOL *copy;
+       CHAR16 *wdesc;
+       size_t path_max;
+       size_t wmax;
+       size_t wlen;
+
+       /* Check basic structure size */
+       if ( len < sizeof ( *load ) ) {
+               DBGC ( load, "EFI load option too short for header:\n" );
+               DBGC_HDA ( load, 0, load, len );
+               return NULL;
+       }
+
+       /* Get length of description */
+       wdesc = ( ( ( void * ) load ) + sizeof ( *load ) );
+       wmax = ( ( len - sizeof ( *load ) ) / sizeof ( wdesc[0] ) );
+       wlen = wcsnlen ( wdesc, wmax );
+       if ( wlen == wmax ) {
+               DBGC ( load, "EFI load option has unterminated "
+                      "description:\n" );
+               DBGC_HDA ( load, 0, load, len );
+               return NULL;
+       }
+
+       /* Get inline device path */
+       path = ( ( ( void * ) load ) + sizeof ( *load ) +
+                ( wlen * sizeof ( wdesc[0] ) ) + 2 /* wNUL */ );
+       path_max = ( len - sizeof ( *load ) - ( wlen * sizeof ( wdesc[0] ) )
+                    - 2 /* wNUL */ );
+       if ( load->FilePathListLength > path_max ) {
+               DBGC ( load, "EFI load option too short for path(s):\n" );
+               DBGC_HDA ( load, 0, load, len );
+               return NULL;
+       }
+
+       /* Check path length */
+       if ( efi_path_check ( path, path_max ) != 0 ) {
+               DBGC ( load, "EFI load option has unterminated device "
+                      "path:\n" );
+               DBGC_HDA ( load, 0, load, len );
+               return NULL;
+       }
+
+       /* Allocate copy of path */
+       copy = malloc ( path_max );
+       if ( ! copy )
+               return NULL;
+       memcpy ( copy, path, path_max );
+
+       return copy;
+}
+
+/**
+ * Get EFI device path for numbered boot option
+ *
+ * @v number           Boot option number
+ * @ret path           EFI device path, or NULL on error
+ *
+ * The caller is responsible for eventually calling free() on the
+ * allocated device path.
+ */
+EFI_DEVICE_PATH_PROTOCOL * efi_boot_path ( unsigned int number ) {
+       EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices;
+       EFI_GUID *guid = &efi_global_variable;
+       CHAR16 wname[ 9 /* "BootXXXX" + wNUL */ ];
+       EFI_LOAD_OPTION *load;
+       EFI_DEVICE_PATH *path;
+       UINT32 attrs;
+       UINTN size;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Construct variable name */
+       efi_snprintf ( wname, ( sizeof ( wname ) / sizeof ( wname[0] ) ),
+                      "Boot%04X", number );
+
+       /* Get variable length */
+       size = 0;
+       if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size,
+                                        NULL ) != EFI_BUFFER_TOO_SMALL ) ) {
+               rc = -EEFI ( efirc );
+               DBGC ( rs, "EFI could not get size of %ls: %s\n",
+                      wname, strerror ( rc ) );
+               goto err_size;
+       }
+
+       /* Allocate temporary buffer for EFI_LOAD_OPTION */
+       load = malloc ( size );
+       if ( ! load ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+
+       /* Read variable */
+       if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size,
+                                        load ) != 0 ) ) {
+               rc = -EEFI ( efirc );
+               DBGC ( rs, "EFI could not read %ls: %s\n",
+                      wname, strerror ( rc ) );
+               goto err_read;
+       }
+       DBGC2 ( rs, "EFI boot option %ls is:\n", wname );
+       DBGC2_HDA ( rs, 0, load, size );
+
+       /* Get device path from load option */
+       path = efi_load_path ( load, size );
+       if ( ! path ) {
+               rc = -EINVAL;
+               DBGC ( rs, "EFI could not parse %ls: %s\n",
+                      wname, strerror ( rc ) );
+               goto err_path;
+       }
+
+       /* Free temporary buffer */
+       free ( load );
+
+       return path;
+
+ err_path:
+ err_read:
+       free ( load );
+ err_alloc:
+ err_size:
+       return NULL;
+}
+
+/**
+ * Get EFI device path for current boot option
+ *
+ * @ret path           EFI device path, or NULL on error
+ *
+ * The caller is responsible for eventually calling free() on the
+ * allocated device path.
+ */
+EFI_DEVICE_PATH_PROTOCOL * efi_current_boot_path ( void ) {
+       EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices;
+       EFI_GUID *guid = &efi_global_variable;
+       CHAR16 wname[] = L"BootCurrent";
+       UINT16 current;
+       UINT32 attrs;
+       UINTN size;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Read current boot option index */
+       size = sizeof ( current );
+       if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size,
+                                        &current ) != 0 ) ) {
+               rc = -EEFI ( efirc );
+               DBGC ( rs, "EFI could not read %ls: %s\n",
+                      wname, strerror ( rc ) );
+               return NULL;
+       }
+
+       /* Get device path from this boot option */
+       return efi_boot_path ( current );
+}
+
 /**
  * Describe object as an EFI device path
  *