From: Michael Brown Date: Thu, 28 Aug 2025 14:34:32 +0000 (+0100) Subject: [efi] Add ability to extract device path from an EFI load option X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c10da8b53c9fc9cda21c28de3226f63cf6c79d8e;p=thirdparty%2Fipxe.git [efi] Add ability to extract device path from an EFI load option 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 --- diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 57fce4028..a37d7b9d7 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -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 ) \ diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index ac3c04987..65ac3d6f5 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -38,6 +38,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include /** @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, + ¤t ) != 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 *