From 7b60a487528a2b6dfa43da179f9ae9ef7ce34e76 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 4 Nov 2022 20:28:09 +0000 Subject: [efi] Clear DMA-coherent buffers before mapping The DMA mapping is performed implicitly as part of the call to dma_alloc(). The current implementation creates the IOMMU mapping for the allocated and potentially uninitialised data before returning to the caller (which will immediately zero out or otherwise initialise the buffer). This leaves a small window within which a malicious PCI device could potentially attempt to retrieve firmware-owned secrets present in the uninitialised buffer. (Note that the hypothetically malicious PCI device has no viable way to know the address of the buffer from which to attempt a DMA read, rendering the attack extremely implausible.) Guard against any such hypothetical attacks by zeroing out the allocated buffer prior to creating the coherent DMA mapping. Suggested-by: Mateusz Siwiec Signed-off-by: Michael Brown --- src/interface/efi/efi_pci.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_pci.c b/src/interface/efi/efi_pci.c index 19e341707..4796201e9 100644 --- a/src/interface/efi/efi_pci.c +++ b/src/interface/efi/efi_pci.c @@ -524,6 +524,9 @@ static void * efipci_dma_alloc ( struct dma_device *dma, goto err_alloc; } + /* Clear buffer */ + memset ( addr, 0, ( pages * EFI_PAGE_SIZE ) ); + /* Map buffer */ if ( ( rc = efipci_dma_map ( dma, map, virt_to_phys ( addr ), ( pages * EFI_PAGE_SIZE ), -- cgit v1.2.3-55-g7522 From d879c8e4d9f3b00c09a7d199a2681705fc5b55d0 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 13 Dec 2022 14:45:44 +0000 Subject: [efi] Provide VLAN configuration protocol UEFI implements VLAN support within the Managed Network Protocol (MNP) driver, which may create child VLAN devices automatically based on stored UEFI variables. These child devices do not themselves provide a raw-packet interface via EFI_SIMPLE_NETWORK_PROTOCOL, and may be consumed only via the EFI_MANAGED_NETWORK_PROTOCOL interface. The device paths constructed for these child devices may conflict with those for the EFI_SIMPLE_NETWORK_PROTOCOL instances that iPXE attempts to install for its own VLAN devices. The upshot is that creating an iPXE VLAN device (e.g. via the "vcreate" command) will fail if the UEFI Managed Network Protocol has already created a device for the same VLAN tag. Fix by providing our own EFI_VLAN_CONFIG_PROTOCOL instance on the same device handle as EFI_SIMPLE_NETWORK_PROTOCOL. This causes the MNP driver to treat iPXE's device as supporting hardware VLAN offload, and it will therefore not attempt to install its own instance of the protocol. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_null.h | 2 + src/include/ipxe/efi/efi_snp.h | 3 + src/include/ipxe/vlan.h | 2 + src/interface/efi/efi_null.c | 42 ++++++++++ src/interface/efi/efi_snp.c | 179 +++++++++++++++++++++++++++++++++++++++- src/net/vlan.c | 3 +- 6 files changed, 227 insertions(+), 4 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_null.h b/src/include/ipxe/efi/efi_null.h index 297457081..d23d36349 100644 --- a/src/include/ipxe/efi/efi_null.h +++ b/src/include/ipxe/efi/efi_null.h @@ -19,9 +19,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include extern void efi_nullify_snp ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ); extern void efi_nullify_nii ( EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *nii ); +extern void efi_nullify_vlan ( EFI_VLAN_CONFIG_PROTOCOL *vcfg ); extern void efi_nullify_name2 ( EFI_COMPONENT_NAME2_PROTOCOL *name2 ); extern void efi_nullify_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file ); extern void efi_nullify_hii ( EFI_HII_CONFIG_ACCESS_PROTOCOL *hii ); diff --git a/src/include/ipxe/efi/efi_snp.h b/src/include/ipxe/efi/efi_snp.h index c278b1d4c..96373b57d 100644 --- a/src/include/ipxe/efi/efi_snp.h +++ b/src/include/ipxe/efi/efi_snp.h @@ -19,6 +19,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include /** SNP transmit completion ring size */ #define EFI_SNP_NUM_TX 32 @@ -51,6 +52,8 @@ struct efi_snp_device { struct list_head rx; /** The network interface identifier */ EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL nii; + /** VLAN configuration protocol */ + EFI_VLAN_CONFIG_PROTOCOL vcfg; /** Component name protocol */ EFI_COMPONENT_NAME2_PROTOCOL name2; /** Load file protocol handle */ diff --git a/src/include/ipxe/vlan.h b/src/include/ipxe/vlan.h index e4baf4cf0..8bf79234b 100644 --- a/src/include/ipxe/vlan.h +++ b/src/include/ipxe/vlan.h @@ -74,6 +74,8 @@ vlan_tag ( struct net_device *netdev ) { return VLAN_TAG ( vlan_tci ( netdev ) ); } +extern struct net_device * vlan_find ( struct net_device *trunk, + unsigned int tag ); extern int vlan_can_be_trunk ( struct net_device *trunk ); extern int vlan_create ( struct net_device *trunk, unsigned int tag, unsigned int priority ); diff --git a/src/interface/efi/efi_null.c b/src/interface/efi/efi_null.c index 29ca5b9b6..d0f0428cc 100644 --- a/src/interface/efi/efi_null.c +++ b/src/interface/efi/efi_null.c @@ -193,6 +193,48 @@ void efi_nullify_nii ( EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *nii ) { nii->Id = ( ( intptr_t ) &efi_null_undi ); } +/****************************************************************************** + * + * VLAN configuration protocol + * + ****************************************************************************** + */ + +static EFI_STATUS EFIAPI +efi_null_vlan_set ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused, + UINT16 tag __unused, UINT8 priority __unused ) { + return EFI_UNSUPPORTED; +} + +static EFI_STATUS EFIAPI +efi_null_vlan_find ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused, + UINT16 *filter __unused, UINT16 *count __unused, + EFI_VLAN_FIND_DATA **entries __unused ) { + return EFI_UNSUPPORTED; +} + +static EFI_STATUS EFIAPI +efi_null_vlan_remove ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused, + UINT16 tag __unused ) { + return EFI_UNSUPPORTED; +} + +static EFI_VLAN_CONFIG_PROTOCOL efi_null_vlan = { + .Set = efi_null_vlan_set, + .Find = efi_null_vlan_find, + .Remove = efi_null_vlan_remove, +}; + +/** + * Nullify VLAN configuration interface + * + * @v vcfg VLAN configuration protocol + */ +void efi_nullify_vlan ( EFI_VLAN_CONFIG_PROTOCOL *vcfg ) { + + memcpy ( vcfg, &efi_null_vlan, sizeof ( *vcfg ) ); +} + /****************************************************************************** * * Component name protocol diff --git a/src/interface/efi/efi_snp.c b/src/interface/efi/efi_snp.c index 6649eb1b0..088a3fdbd 100644 --- a/src/interface/efi/efi_snp.c +++ b/src/interface/efi/efi_snp.c @@ -1488,6 +1488,164 @@ static EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL efi_snp_device_nii = { .Ipv6Supported = TRUE, /* This is a raw packet interface, FFS! */ }; +/****************************************************************************** + * + * VLAN configuration protocol + * + ****************************************************************************** + */ + +/** + * Create or modify VLAN device + * + * @v vcfg VLAN configuration protocol + * @v tag VLAN tag + * @v priority Default VLAN priority + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_vlan_set ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, + UINT16 tag, UINT8 priority ) { + struct efi_snp_device *snpdev = + container_of ( vcfg, struct efi_snp_device, vcfg ); + struct net_device *trunk = snpdev->netdev; + struct efi_saved_tpl tpl; + int rc; + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Create or modify VLAN device */ + if ( ( rc = vlan_create ( trunk, tag, priority ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not create VLAN tag %d: %s\n", + snpdev, tag, strerror ( rc ) ); + goto err_create; + } + DBGC ( snpdev, "SNPDEV %p created VLAN tag %d priority %d\n", + snpdev, tag, priority ); + + err_create: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Find VLAN device(s) + * + * @v vcfg VLAN configuration protocol + * @v filter VLAN tag, or NULL to find all VLANs + * @v count Number of VLANs + * @v entries List of VLANs + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_vlan_find ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, + UINT16 *filter, UINT16 *count, + EFI_VLAN_FIND_DATA **entries ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_snp_device *snpdev = + container_of ( vcfg, struct efi_snp_device, vcfg ); + struct net_device *trunk = snpdev->netdev; + struct net_device *vlan; + struct efi_saved_tpl tpl; + EFI_VLAN_FIND_DATA *entry; + VOID *buffer; + unsigned int tag; + unsigned int tci; + size_t len; + EFI_STATUS efirc; + int rc; + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Count number of matching VLANs */ + *count = 0; + for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) { + if ( filter && ( tag != *filter ) ) + continue; + if ( ! ( vlan = vlan_find ( trunk, tag ) ) ) + continue; + (*count)++; + } + + /* Allocate buffer to hold results */ + len = ( (*count) * sizeof ( *entry ) ); + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, len, + &buffer ) ) != 0 ) { + rc = -EEFI ( efirc ); + goto err_alloc; + } + + /* Fill in buffer */ + *entries = buffer; + entry = *entries; + for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) { + if ( filter && ( tag != *filter ) ) + continue; + if ( ! ( vlan = vlan_find ( trunk, tag ) ) ) + continue; + tci = vlan_tci ( vlan ); + entry->VlanId = VLAN_TAG ( tci ); + entry->Priority = VLAN_PRIORITY ( tci ); + assert ( entry->VlanId == tag ); + entry++; + } + assert ( entry == &(*entries)[*count] ); + + /* Success */ + rc = 0; + + err_alloc: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Remove VLAN device + * + * @v vcfg VLAN configuration protocol + * @v tag VLAN tag + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_vlan_remove ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, + UINT16 tag ) { + struct efi_snp_device *snpdev = + container_of ( vcfg, struct efi_snp_device, vcfg ); + struct net_device *trunk = snpdev->netdev; + struct net_device *vlan; + struct efi_saved_tpl tpl; + int rc; + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Identify VLAN device */ + vlan = vlan_find ( trunk, tag ); + if ( ! vlan ) { + DBGC ( snpdev, "SNPDEV %p could not find VLAN tag %d\n", + snpdev, tag ); + rc = -ENOENT; + goto err_find; + } + + /* Remove VLAN device */ + vlan_destroy ( vlan ); + DBGC ( snpdev, "SNPDEV %p removed VLAN tag %d\n", snpdev, tag ); + + /* Success */ + rc = 0; + + err_find: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** VLAN configuration protocol */ +static EFI_VLAN_CONFIG_PROTOCOL efi_vlan = { + .Set = efi_vlan_set, + .Find = efi_vlan_find, + .Remove = efi_vlan_remove, +}; + /****************************************************************************** * * Component name protocol @@ -1627,6 +1785,8 @@ static int efi_snp_probe ( struct net_device *netdev ) { struct efi_snp_device *snpdev; unsigned int ifcnt; void *interface; + unsigned int tci; + char vlan_name[ 12 /* ", VLAN xxxx" + NUL */ ]; int leak = 0; EFI_STATUS efirc; int rc; @@ -1687,17 +1847,27 @@ static int efi_snp_probe ( struct net_device *netdev ) { efi_snp_undi.Fudge -= efi_undi_checksum ( &efi_snp_undi, sizeof ( efi_snp_undi ) ); + /* Populate the VLAN configuration protocol */ + memcpy ( &snpdev->vcfg, &efi_vlan, sizeof ( snpdev->vcfg ) ); + /* Populate the component name structure */ efi_snprintf ( snpdev->driver_name, ( sizeof ( snpdev->driver_name ) / sizeof ( snpdev->driver_name[0] ) ), "%s %s", product_short_name, netdev->dev->driver_name ); + tci = vlan_tci ( netdev ); + if ( tci ) { + snprintf ( vlan_name, sizeof ( vlan_name ), ", VLAN %d", + VLAN_TAG ( tci ) ); + } else { + vlan_name[0] = '\0'; + } efi_snprintf ( snpdev->controller_name, ( sizeof ( snpdev->controller_name ) / sizeof ( snpdev->controller_name[0] ) ), - "%s %s (%s, %s)", product_short_name, + "%s %s (%s, %s%s)", product_short_name, netdev->dev->driver_name, netdev->dev->name, - netdev_addr ( netdev ) ); + netdev_addr ( netdev ), vlan_name ); snpdev->name2.GetDriverName = efi_snp_get_driver_name; snpdev->name2.GetControllerName = efi_snp_get_controller_name; snpdev->name2.SupportedLanguages = "en"; @@ -1725,6 +1895,7 @@ static int efi_snp_probe ( struct net_device *netdev ) { &efi_device_path_protocol_guid, snpdev->path, &efi_nii_protocol_guid, &snpdev->nii, &efi_nii31_protocol_guid, &snpdev->nii, + &efi_vlan_config_protocol_guid, &snpdev->vcfg, &efi_component_name2_protocol_guid, &snpdev->name2, &efi_load_file_protocol_guid, &snpdev->load_file, NULL ) ) != 0 ) { @@ -1811,6 +1982,7 @@ static int efi_snp_probe ( struct net_device *netdev ) { &efi_device_path_protocol_guid, snpdev->path, &efi_nii_protocol_guid, &snpdev->nii, &efi_nii31_protocol_guid, &snpdev->nii, + &efi_vlan_config_protocol_guid, &snpdev->vcfg, &efi_component_name2_protocol_guid, &snpdev->name2, &efi_load_file_protocol_guid, &snpdev->load_file, NULL ) ) != 0 ) { @@ -1820,6 +1992,7 @@ static int efi_snp_probe ( struct net_device *netdev ) { } efi_nullify_snp ( &snpdev->snp ); efi_nullify_nii ( &snpdev->nii ); + efi_nullify_vlan ( &snpdev->vcfg ); efi_nullify_name2 ( &snpdev->name2 ); efi_nullify_load_file ( &snpdev->load_file ); err_install_protocol_interface: @@ -1899,6 +2072,7 @@ static void efi_snp_remove ( struct net_device *netdev ) { &efi_device_path_protocol_guid, snpdev->path, &efi_nii_protocol_guid, &snpdev->nii, &efi_nii31_protocol_guid, &snpdev->nii, + &efi_vlan_config_protocol_guid, &snpdev->vcfg, &efi_component_name2_protocol_guid, &snpdev->name2, &efi_load_file_protocol_guid, &snpdev->load_file, NULL ) ) != 0 ) ) { @@ -1908,6 +2082,7 @@ static void efi_snp_remove ( struct net_device *netdev ) { } efi_nullify_snp ( &snpdev->snp ); efi_nullify_nii ( &snpdev->nii ); + efi_nullify_vlan ( &snpdev->vcfg ); efi_nullify_name2 ( &snpdev->name2 ); efi_nullify_load_file ( &snpdev->load_file ); if ( ! leak ) diff --git a/src/net/vlan.c b/src/net/vlan.c index fe4488614..81ec623f2 100644 --- a/src/net/vlan.c +++ b/src/net/vlan.c @@ -199,8 +199,7 @@ static void vlan_sync ( struct net_device *netdev ) { * @v tag VLAN tag * @ret netdev VLAN device, if any */ -static struct net_device * vlan_find ( struct net_device *trunk, - unsigned int tag ) { +struct net_device * vlan_find ( struct net_device *trunk, unsigned int tag ) { struct net_device *netdev; struct vlan_device *vlan; -- cgit v1.2.3-55-g7522 From 0f3ace92c6f7bd60a6688c0bacfa5aeacfdb5b73 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 22 Dec 2022 13:28:06 +0000 Subject: [efi] Allow passing a NULL device path to path utility functions Signed-off-by: Michael Brown --- src/interface/efi/efi_local.c | 2 +- src/interface/efi/efi_path.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_local.c b/src/interface/efi/efi_local.c index 4ebca5726..d8edb6263 100644 --- a/src/interface/efi/efi_local.c +++ b/src/interface/efi/efi_local.c @@ -425,7 +425,7 @@ static int efi_local_open_resolved ( struct efi_local *local, static int efi_local_open_path ( struct efi_local *local, const char *path ) { FILEPATH_DEVICE_PATH *fp = container_of ( efi_loaded_image->FilePath, FILEPATH_DEVICE_PATH, Header); - size_t fp_len = ( fp ? efi_path_len ( &fp->Header ) : 0 ); + size_t fp_len = efi_path_len ( &fp->Header ); char base[ fp_len / 2 /* Cannot exceed this length */ ]; size_t remaining = sizeof ( base ); size_t len; diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index bae0ac4b5..1a95a3b9a 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -43,12 +43,12 @@ /** * Find end of device path * - * @v path Path to device - * @ret path_end End of device path + * @v path Device path, or NULL + * @ret path_end End of device path, or NULL */ EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { - while ( path->Type != END_DEVICE_PATH_TYPE ) { + while ( path && ( path->Type != END_DEVICE_PATH_TYPE ) ) { path = ( ( ( void * ) path ) + /* There's this amazing new-fangled thing known as * a UINT16, but who wants to use one of those? */ @@ -61,7 +61,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { /** * Find length of device path (excluding terminator) * - * @v path Path to device + * @v path Device path, or NULL * @ret path_len Length of device path */ size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ) { -- cgit v1.2.3-55-g7522 From 099e4d39b355a10b0bc7d23bb0e96615bf06470b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 22 Dec 2022 13:33:38 +0000 Subject: [efi] Expose efi_path_next() utility function Provide a single central implementation of the logic for stepping through elements of an EFI device path. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_path.h | 2 ++ src/interface/efi/efi_local.c | 20 ++++++++++---------- src/interface/efi/efi_path.c | 34 +++++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 76ded728c..18810a751 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -21,6 +21,8 @@ struct fcp_description; struct ib_srp_device; struct usb_function; +extern EFI_DEVICE_PATH_PROTOCOL * +efi_path_next ( 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 ); diff --git a/src/interface/efi/efi_local.c b/src/interface/efi/efi_local.c index d8edb6263..f54f5daea 100644 --- a/src/interface/efi/efi_local.c +++ b/src/interface/efi/efi_local.c @@ -419,14 +419,15 @@ static int efi_local_open_resolved ( struct efi_local *local, * Open specified path * * @v local Local file - * @v path Path to file + * @v filename Path to file relative to our own image * @ret rc Return status code */ -static int efi_local_open_path ( struct efi_local *local, const char *path ) { - FILEPATH_DEVICE_PATH *fp = container_of ( efi_loaded_image->FilePath, - FILEPATH_DEVICE_PATH, Header); - size_t fp_len = efi_path_len ( &fp->Header ); - char base[ fp_len / 2 /* Cannot exceed this length */ ]; +static int efi_local_open_path ( struct efi_local *local, + const char *filename ) { + EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image->FilePath; + EFI_DEVICE_PATH_PROTOCOL *next; + FILEPATH_DEVICE_PATH *fp; + char base[ efi_path_len ( path ) / 2 /* Cannot exceed this length */ ]; size_t remaining = sizeof ( base ); size_t len; char *resolved; @@ -436,13 +437,12 @@ static int efi_local_open_path ( struct efi_local *local, const char *path ) { /* Construct base path to our own image, if possible */ memset ( base, 0, sizeof ( base ) ); tmp = base; - while ( fp && ( fp->Header.Type != END_DEVICE_PATH_TYPE ) ) { + for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { + fp = container_of ( path, FILEPATH_DEVICE_PATH, Header ); len = snprintf ( tmp, remaining, "%ls", fp->PathName ); assert ( len < remaining ); tmp += len; remaining -= len; - fp = ( ( ( void * ) fp ) + ( ( fp->Header.Length[1] << 8 ) | - fp->Header.Length[0] ) ); } DBGC2 ( local, "LOCAL %p base path \"%s\"\n", local, base ); @@ -454,7 +454,7 @@ static int efi_local_open_path ( struct efi_local *local, const char *path ) { } /* Resolve path */ - resolved = resolve_path ( base, path ); + resolved = resolve_path ( base, filename ); if ( ! resolved ) { rc = -ENOMEM; goto err_resolve; diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 1a95a3b9a..937d3c704 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -40,6 +40,31 @@ * */ +/** + * Find next element in device path + * + * @v path Device path, or NULL + * @v next Next element in device path, or NULL if at end + */ +EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ) { + + /* Check for non-existent device path */ + if ( ! path ) + return NULL; + + /* Check for end of device path */ + if ( path->Type == END_DEVICE_PATH_TYPE ) + return NULL; + + /* Move to next component of the device path */ + path = ( ( ( void * ) path ) + + /* There's this amazing new-fangled thing known as + * a UINT16, but who wants to use one of those? */ + ( ( path->Length[1] << 8 ) | path->Length[0] ) ); + + return path; +} + /** * Find end of device path * @@ -47,12 +72,11 @@ * @ret path_end End of device path, or NULL */ EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_DEVICE_PATH_PROTOCOL *next; - while ( path && ( path->Type != END_DEVICE_PATH_TYPE ) ) { - path = ( ( ( void * ) path ) + - /* There's this amazing new-fangled thing known as - * a UINT16, but who wants to use one of those? */ - ( ( path->Length[1] << 8 ) | path->Length[0] ) ); + /* Find end of device path */ + while ( ( next = efi_path_next ( path ) ) != NULL ) { + path = next; } return path; -- cgit v1.2.3-55-g7522 From b9571ca12ed80472051663e2618dfe083f995da3 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 22 Dec 2022 14:27:56 +0000 Subject: [efi] Add efi_path_vlan() utility function EFI provides no API for determining the VLAN tag (if any) for a specified device handle. There is the EFI_VLAN_CONFIG_PROTOCOL, but that exists only on the trunk device handle (not on the VLAN device handle), and provides no way to match VLAN tags against the trunk device's child device handles. The EDK2 codebase seems to rely solely on the device path to determine the VLAN tag for a specified device handle: both NetLibGetVlanId() and BmGetNetworkDescription() will parse the device path to search for a VLAN_DEVICE_PATH component. Add efi_path_vlan() which uses the same device path parsing logic to determine the VLAN tag. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_path.h | 1 + src/interface/efi/efi_path.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 18810a751..9dea74b5a 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -26,6 +26,7 @@ efi_path_next ( 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 unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ); extern EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ); extern EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 937d3c704..fb0f3059d 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -94,6 +94,29 @@ size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ) { return ( ( ( void * ) end ) - ( ( void * ) path ) ); } +/** + * Get VLAN tag from device path + * + * @v path Device path + * @ret tag VLAN tag, or 0 if not a VLAN + */ +unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_DEVICE_PATH_PROTOCOL *next; + VLAN_DEVICE_PATH *vlan; + + /* Search for VLAN device path */ + for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { + if ( ( path->Type == MESSAGING_DEVICE_PATH ) && + ( path->SubType == MSG_VLAN_DP ) ) { + vlan = container_of ( path, VLAN_DEVICE_PATH, Header ); + return vlan->VlanId; + } + } + + /* No VLAN device path found */ + return 0; +} + /** * Concatenate EFI device paths * -- cgit v1.2.3-55-g7522 From 60b5532cfc1393a8d34cece1b49f953bd72c303c Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 22 Dec 2022 14:59:29 +0000 Subject: [cachedhcp] Include VLAN tag in filter for applying cached DHCPACK When chainloading iPXE from a VLAN device, the MAC address within the cached DHCPACK will match the MAC address of the trunk device created by iPXE, and the cached DHCPACK will then end up being erroneously applied to the trunk device. This tends to break outbound IPv4 routing, since both the trunk and VLAN devices will have the same assigned IPv4 address. Fix by recording the VLAN tag along with the cached DHCPACK, and treating the VLAN tag as part of the filter used to match the cached DHCPACK against candidate network devices. Signed-off-by: Michael Brown --- src/arch/x86/interface/pcbios/bios_cachedhcp.c | 2 +- src/core/cachedhcp.c | 28 ++++++++++++++++++++------ src/include/ipxe/cachedhcp.h | 3 ++- src/include/ipxe/efi/efi_cachedhcp.h | 3 ++- src/interface/efi/efi_cachedhcp.c | 17 +++++++++++----- src/interface/efi/efiprefix.c | 3 ++- 6 files changed, 41 insertions(+), 15 deletions(-) (limited to 'src/interface') diff --git a/src/arch/x86/interface/pcbios/bios_cachedhcp.c b/src/arch/x86/interface/pcbios/bios_cachedhcp.c index 277c40d6f..bea803d6e 100644 --- a/src/arch/x86/interface/pcbios/bios_cachedhcp.c +++ b/src/arch/x86/interface/pcbios/bios_cachedhcp.c @@ -59,7 +59,7 @@ static void cachedhcp_init ( void ) { } /* Record cached DHCPACK */ - if ( ( rc = cachedhcp_record ( &cached_dhcpack, + if ( ( rc = cachedhcp_record ( &cached_dhcpack, 0, phys_to_user ( cached_dhcpack_phys ), sizeof ( BOOTPLAYER_t ) ) ) != 0 ) { DBGC ( colour, "CACHEDHCP could not record DHCPACK: %s\n", diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c index c4ca46e3a..ef2146626 100644 --- a/src/core/cachedhcp.c +++ b/src/core/cachedhcp.c @@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include /** @file @@ -43,6 +44,8 @@ struct cached_dhcp_packet { const char *name; /** DHCP packet (if any) */ struct dhcp_packet *dhcppkt; + /** VLAN tag (if applicable) */ + unsigned int vlan; }; /** Cached DHCPACK */ @@ -136,15 +139,26 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache, * matches this network device. */ if ( memcmp ( ll_addr, chaddr, ll_addr_len ) != 0 ) { - DBGC ( colour, "CACHEDHCP %s does not match %s\n", - cache->name, netdev->name ); + DBGC ( colour, "CACHEDHCP %s %s does not match %s\n", + cache->name, ll_protocol->ntoa ( chaddr ), + netdev->name ); + return 0; + } + + /* Do nothing unless cached packet's VLAN tag matches + * this network device. + */ + if ( vlan_tag ( netdev ) != cache->vlan ) { + DBGC ( colour, "CACHEDHCP %s VLAN %d does not match " + "%s\n", cache->name, cache->vlan, + netdev->name ); return 0; } - DBGC ( colour, "CACHEDHCP %s is for %s\n", - cache->name, netdev->name ); /* Use network device's settings block */ settings = netdev_settings ( netdev ); + DBGC ( colour, "CACHEDHCP %s is for %s\n", + cache->name, netdev->name ); } /* Register settings */ @@ -165,12 +179,13 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache, * Record cached DHCP packet * * @v cache Cached DHCP packet + * @v vlan VLAN tag, if any * @v data DHCPACK packet buffer * @v max_len Maximum possible length * @ret rc Return status code */ -int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data, - size_t max_len ) { +int cachedhcp_record ( struct cached_dhcp_packet *cache, unsigned int vlan, + userptr_t data, size_t max_len ) { struct dhcp_packet *dhcppkt; struct dhcp_packet *tmp; struct dhcphdr *dhcphdr; @@ -225,6 +240,7 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data, DBGC ( colour, "CACHEDHCP %s at %#08lx+%#zx/%#zx\n", cache->name, user_to_phys ( data, 0 ), len, max_len ); cache->dhcppkt = dhcppkt; + cache->vlan = vlan; return 0; } diff --git a/src/include/ipxe/cachedhcp.h b/src/include/ipxe/cachedhcp.h index 39ce74543..4ce4a9f2a 100644 --- a/src/include/ipxe/cachedhcp.h +++ b/src/include/ipxe/cachedhcp.h @@ -18,7 +18,8 @@ extern struct cached_dhcp_packet cached_dhcpack; extern struct cached_dhcp_packet cached_proxydhcp; extern struct cached_dhcp_packet cached_pxebs; -extern int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data, +extern int cachedhcp_record ( struct cached_dhcp_packet *cache, + unsigned int vlan, userptr_t data, size_t max_len ); #endif /* _IPXE_CACHEDHCP_H */ diff --git a/src/include/ipxe/efi/efi_cachedhcp.h b/src/include/ipxe/efi/efi_cachedhcp.h index cd60d4095..5968a1ea2 100644 --- a/src/include/ipxe/efi/efi_cachedhcp.h +++ b/src/include/ipxe/efi/efi_cachedhcp.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -extern int efi_cachedhcp_record ( EFI_HANDLE device ); +extern int efi_cachedhcp_record ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ); #endif /* _IPXE_EFI_CACHEDHCP_H */ diff --git a/src/interface/efi/efi_cachedhcp.c b/src/interface/efi/efi_cachedhcp.c index 1d4b98fd6..b9e49cf48 100644 --- a/src/interface/efi/efi_cachedhcp.c +++ b/src/interface/efi/efi_cachedhcp.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include @@ -40,10 +41,13 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * Record cached DHCP packet * * @v device Device handle + * @v path Device path * @ret rc Return status code */ -int efi_cachedhcp_record ( EFI_HANDLE device ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; +int efi_cachedhcp_record ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + unsigned int vlan; union { EFI_PXE_BASE_CODE_PROTOCOL *pxe; void *interface; @@ -52,6 +56,9 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) { EFI_STATUS efirc; int rc; + /* Get VLAN tag, if any */ + vlan = efi_path_vlan ( path ); + /* Look for a PXE base code instance on the image's device handle */ if ( ( efirc = bs->OpenProtocol ( device, &efi_pxe_base_code_protocol_guid, @@ -75,7 +82,7 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) { /* Record DHCPACK, if present */ if ( mode->DhcpAckReceived && - ( ( rc = cachedhcp_record ( &cached_dhcpack, + ( ( rc = cachedhcp_record ( &cached_dhcpack, vlan, virt_to_user ( &mode->DhcpAck ), sizeof ( mode->DhcpAck ) ) ) != 0 ) ) { DBGC ( device, "EFI %s could not record DHCPACK: %s\n", @@ -85,7 +92,7 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) { /* Record ProxyDHCPOFFER, if present */ if ( mode->ProxyOfferReceived && - ( ( rc = cachedhcp_record ( &cached_proxydhcp, + ( ( rc = cachedhcp_record ( &cached_proxydhcp, vlan, virt_to_user ( &mode->ProxyOffer ), sizeof ( mode->ProxyOffer ) ) ) != 0)){ DBGC ( device, "EFI %s could not record ProxyDHCPOFFER: %s\n", @@ -95,7 +102,7 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) { /* Record PxeBSACK, if present */ if ( mode->PxeReplyReceived && - ( ( rc = cachedhcp_record ( &cached_pxebs, + ( ( rc = cachedhcp_record ( &cached_pxebs, vlan, virt_to_user ( &mode->PxeReply ), sizeof ( mode->PxeReply ) ) ) != 0)){ DBGC ( device, "EFI %s could not record PXEBSACK: %s\n", diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c index 126c813d7..7286b3e03 100644 --- a/src/interface/efi/efiprefix.c +++ b/src/interface/efi/efiprefix.c @@ -78,12 +78,13 @@ EFI_STATUS EFIAPI _efi_start ( EFI_HANDLE image_handle, */ static void efi_init_application ( void ) { EFI_HANDLE device = efi_loaded_image->DeviceHandle; + EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path; /* Identify autoboot device, if any */ efi_set_autoboot_ll_addr ( device ); /* Store cached DHCP packet, if any */ - efi_cachedhcp_record ( device ); + efi_cachedhcp_record ( device, path ); /* Load autoexec script, if any */ efi_autoexec_load ( device ); -- cgit v1.2.3-55-g7522 From 47af48012e2afaaf56108466fb967009670660bb Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sat, 14 Jan 2023 00:09:20 +0000 Subject: [netdevice] Separate concept of scope ID from network device name index The network device index currently serves two purposes: acting as a sequential index for network device names ("net0", "net1", etc), and acting as an opaque unique integer identifier used in socket address scope IDs. There is no particular need for these usages to be linked, and it can lead to situations in which devices are named unexpectedly. For example: if a system has two network devices "net0" and "net1", a VLAN is created as "net1-42", and then a USB NIC is connected, then the USB NIC will be named "net3" rather than the expected "net2" since the VLAN device "net1-42" will have consumed an index. Separate the usages: rename the "index" field to "scope_id" (matching its one and only use case), and assign the name without reference to the scope ID by finding the first unused name. For consistency, assign the scope ID by similarly finding the first unused scope ID. Signed-off-by: Michael Brown --- src/include/ipxe/netdevice.h | 6 +++--- src/interface/efi/efi_pxe.c | 2 +- src/interface/efi/efi_snp.c | 4 ++-- src/net/ipv4.c | 2 +- src/net/ipv6.c | 12 ++++++------ src/net/ndp.c | 4 ++-- src/net/netdevice.c | 31 +++++++++++++++++-------------- src/net/peerdisc.c | 2 +- src/net/udp/dhcpv6.c | 2 +- src/tests/ipv6_test.c | 4 ++-- 10 files changed, 36 insertions(+), 33 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/netdevice.h b/src/include/ipxe/netdevice.h index 294f7b367..29358dba0 100644 --- a/src/include/ipxe/netdevice.h +++ b/src/include/ipxe/netdevice.h @@ -356,8 +356,8 @@ struct net_device { struct list_head list; /** List of open network devices */ struct list_head open_list; - /** Index of this network device */ - unsigned int index; + /** Scope ID */ + unsigned int scope_id; /** Name of this network device */ char name[NETDEV_NAME_LEN]; /** Underlying hardware device */ @@ -726,7 +726,7 @@ extern void netdev_close ( struct net_device *netdev ); extern void unregister_netdev ( struct net_device *netdev ); extern void netdev_irq ( struct net_device *netdev, int enable ); extern struct net_device * find_netdev ( const char *name ); -extern struct net_device * find_netdev_by_index ( unsigned int index ); +extern struct net_device * find_netdev_by_scope_id ( unsigned int scope_id ); extern struct net_device * find_netdev_by_location ( unsigned int bus_type, unsigned int location ); extern struct net_device * diff --git a/src/interface/efi/efi_pxe.c b/src/interface/efi/efi_pxe.c index 15224a5e4..843ebb5e7 100644 --- a/src/interface/efi/efi_pxe.c +++ b/src/interface/efi/efi_pxe.c @@ -199,7 +199,7 @@ static void efi_pxe_ip_sockaddr ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip, memset ( sockaddr, 0, sizeof ( *sockaddr ) ); sockaddr->sa.sa_family = pxe->tcpip->sa_family; memcpy ( &sockaddr->se.se_addr, ip, pxe->net->net_addr_len ); - sockaddr->se.se_scope_id = pxe->netdev->index; + sockaddr->se.se_scope_id = pxe->netdev->scope_id; } /** diff --git a/src/interface/efi/efi_snp.c b/src/interface/efi/efi_snp.c index 088a3fdbd..c4f7d4ea8 100644 --- a/src/interface/efi/efi_snp.c +++ b/src/interface/efi/efi_snp.c @@ -934,11 +934,11 @@ static uint8_t efi_undi_checksum ( void *data, size_t len ) { */ static unsigned int efi_undi_ifnum ( struct efi_snp_device *snpdev ) { - /* iPXE network device indexes are one-based (leaving zero + /* iPXE network device scope IDs are one-based (leaving zero * meaning "unspecified"). UNDI interface numbers are * zero-based. */ - return ( snpdev->netdev->index - 1 ); + return ( snpdev->netdev->scope_id - 1 ); } /** diff --git a/src/net/ipv4.c b/src/net/ipv4.c index b9ce5e7f7..b91fa2ad0 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -163,7 +163,7 @@ static struct ipv4_miniroute * ipv4_route ( unsigned int scope_id, /* If destination is non-global, and the scope ID * matches this network device, then use this route. */ - if ( miniroute->netdev->index == scope_id ) + if ( miniroute->netdev->scope_id == scope_id ) return miniroute; } else { diff --git a/src/net/ipv6.c b/src/net/ipv6.c index 901203c40..ef5e51daa 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -330,7 +330,7 @@ struct ipv6_miniroute * ipv6_route ( unsigned int scope_id, /* Skip entries with a non-matching scope ID, if * destination specifies a scope ID. */ - if ( scope_id && ( miniroute->netdev->index != scope_id ) ) + if ( scope_id && ( miniroute->netdev->scope_id != scope_id ) ) continue; /* Skip entries that are out of scope */ @@ -789,12 +789,12 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, src.sin6.sin6_family = AF_INET6; memcpy ( &src.sin6.sin6_addr, &iphdr->src, sizeof ( src.sin6.sin6_addr ) ); - src.sin6.sin6_scope_id = netdev->index; + src.sin6.sin6_scope_id = netdev->scope_id; memset ( &dest, 0, sizeof ( dest ) ); dest.sin6.sin6_family = AF_INET6; memcpy ( &dest.sin6.sin6_addr, &iphdr->dest, sizeof ( dest.sin6.sin6_addr ) ); - dest.sin6.sin6_scope_id = netdev->index; + dest.sin6.sin6_scope_id = netdev->scope_id; iob_pull ( iobuf, hdrlen ); pshdr_csum = ipv6_pshdr_chksum ( iphdr, iob_len ( iobuf ), next_header, TCPIP_EMPTY_CSUM ); @@ -957,7 +957,7 @@ static const char * ipv6_sock_ntoa ( struct sockaddr *sa ) { /* Identify network device, if applicable */ if ( IN6_IS_ADDR_LINKLOCAL ( in ) || IN6_IS_ADDR_MULTICAST ( in ) ) { - netdev = find_netdev_by_index ( sin6->sin6_scope_id ); + netdev = find_netdev_by_scope_id ( sin6->sin6_scope_id ); netdev_name = ( netdev ? netdev->name : "UNKNOWN" ); } else { netdev_name = NULL; @@ -1020,7 +1020,7 @@ static int ipv6_sock_aton ( const char *string, struct sockaddr *sa ) { rc = -ENODEV; goto err_find_netdev; } - sin6->sin6_scope_id = netdev->index; + sin6->sin6_scope_id = netdev->scope_id; } else if ( IN6_IS_ADDR_LINKLOCAL ( &in ) || IN6_IS_ADDR_MULTICAST ( &in ) ) { @@ -1031,7 +1031,7 @@ static int ipv6_sock_aton ( const char *string, struct sockaddr *sa ) { */ netdev = last_opened_netdev(); if ( netdev ) - sin6->sin6_scope_id = netdev->index; + sin6->sin6_scope_id = netdev->scope_id; } /* Copy IPv6 address portion to socket address */ diff --git a/src/net/ndp.c b/src/net/ndp.c index c8e8ebad3..373a9360b 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -140,7 +140,7 @@ static int ndp_tx_request ( struct net_device *netdev, /* Construct multicast destination address */ memset ( &sin6_dest, 0, sizeof ( sin6_dest ) ); sin6_dest.sin6_family = AF_INET6; - sin6_dest.sin6_scope_id = netdev->index; + sin6_dest.sin6_scope_id = netdev->scope_id; ipv6_solicited_node ( &sin6_dest.sin6_addr, net_dest ); /* Construct neighbour header */ @@ -177,7 +177,7 @@ static int ndp_tx_router_solicitation ( struct net_device *netdev ) { /* Construct multicast destination address */ memset ( &sin6_dest, 0, sizeof ( sin6_dest ) ); sin6_dest.sin6_family = AF_INET6; - sin6_dest.sin6_scope_id = netdev->index; + sin6_dest.sin6_scope_id = netdev->scope_id; ipv6_all_routers ( &sin6_dest.sin6_addr ); /* Construct router solicitation */ diff --git a/src/net/netdevice.c b/src/net/netdevice.c index 51d1831cc..597c62285 100644 --- a/src/net/netdevice.c +++ b/src/net/netdevice.c @@ -55,9 +55,6 @@ struct list_head net_devices = LIST_HEAD_INIT ( net_devices ); /** List of open network devices, in reverse order of opening */ static struct list_head open_net_devices = LIST_HEAD_INIT ( open_net_devices ); -/** Network device index */ -static unsigned int netdev_index = 0; - /** Network polling profiler */ static struct profiler net_poll_profiler __profiler = { .name = "net.poll" }; @@ -723,6 +720,7 @@ int register_netdev ( struct net_device *netdev ) { struct ll_protocol *ll_protocol = netdev->ll_protocol; struct net_driver *driver; struct net_device *duplicate; + unsigned int i; uint32_t seed; int rc; @@ -757,12 +755,21 @@ int register_netdev ( struct net_device *netdev ) { goto err_duplicate; } - /* Record device index and create device name */ + /* Assign a unique device name, if not already set */ if ( netdev->name[0] == '\0' ) { - snprintf ( netdev->name, sizeof ( netdev->name ), "net%d", - netdev_index ); + for ( i = 0 ; ; i++ ) { + snprintf ( netdev->name, sizeof ( netdev->name ), + "net%d", i ); + if ( find_netdev ( netdev->name ) == NULL ) + break; + } + } + + /* Assign a unique non-zero scope ID */ + for ( netdev->scope_id = 1 ; ; netdev->scope_id++ ) { + if ( find_netdev_by_scope_id ( netdev->scope_id ) == NULL ) + break; } - netdev->index = ++netdev_index; /* Use least significant bits of the link-layer address to * improve the randomness of the (non-cryptographic) random @@ -916,10 +923,6 @@ void unregister_netdev ( struct net_device *netdev ) { DBGC ( netdev, "NETDEV %s unregistered\n", netdev->name ); list_del ( &netdev->list ); netdev_put ( netdev ); - - /* Reset network device index if no devices remain */ - if ( list_empty ( &net_devices ) ) - netdev_index = 0; } /** Enable or disable interrupts @@ -962,17 +965,17 @@ struct net_device * find_netdev ( const char *name ) { } /** - * Get network device by index + * Get network device by scope ID * * @v index Network device index * @ret netdev Network device, or NULL */ -struct net_device * find_netdev_by_index ( unsigned int index ) { +struct net_device * find_netdev_by_scope_id ( unsigned int scope_id ) { struct net_device *netdev; /* Identify network device by index */ list_for_each_entry ( netdev, &net_devices, list ) { - if ( netdev->index == index ) + if ( netdev->scope_id == scope_id ) return netdev; } diff --git a/src/net/peerdisc.c b/src/net/peerdisc.c index d7e0d2989..86ff94a87 100644 --- a/src/net/peerdisc.c +++ b/src/net/peerdisc.c @@ -189,7 +189,7 @@ static void peerdisc_socket_tx ( const char *uuid, const char *id ) { /* Skip unopened network devices */ if ( ! netdev_is_open ( netdev ) ) continue; - address.st.st_scope_id = netdev->index; + address.st.st_scope_id = netdev->scope_id; /* Discard request (for test purposes) if applicable */ if ( inject_fault ( PEERDISC_DISCARD_RATE ) ) diff --git a/src/net/udp/dhcpv6.c b/src/net/udp/dhcpv6.c index 253032e4e..28c6f7be4 100644 --- a/src/net/udp/dhcpv6.c +++ b/src/net/udp/dhcpv6.c @@ -955,7 +955,7 @@ int start_dhcpv6 ( struct interface *job, struct net_device *netdev, addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT ); addresses.server.sin6.sin6_family = AF_INET6; ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr ); - addresses.server.sin6.sin6_scope_id = netdev->index; + addresses.server.sin6.sin6_scope_id = netdev->scope_id; addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT ); /* Construct client DUID from system UUID */ diff --git a/src/tests/ipv6_test.c b/src/tests/ipv6_test.c index de8edc8ad..3b7d813a5 100644 --- a/src/tests/ipv6_test.c +++ b/src/tests/ipv6_test.c @@ -127,7 +127,7 @@ static const struct in6_addr sample_multicast = { /** Dummy network device used for routing tests */ static struct net_device ipv6_test_netdev = { .refcnt = REF_INIT ( ref_no_free ), - .index = 42, + .scope_id = 42, .state = NETDEV_OPEN, }; @@ -349,7 +349,7 @@ static void ipv6_route_okx ( struct ipv6_test_table *table, const char *dest, /* Perform routing */ actual = &in_dest; - miniroute = ipv6_route ( ipv6_test_netdev.index, &actual ); + miniroute = ipv6_route ( ipv6_test_netdev.scope_id, &actual ); /* Validate result */ if ( src ) { -- cgit v1.2.3-55-g7522 From 5a2fa6040e17562ce742df09aa20b8774b3879c5 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 15 Jan 2023 21:36:08 +0000 Subject: [autoboot] Include VLAN tag in filter for identifying autoboot device When chainloading iPXE from a VLAN device, the MAC address of the loaded image's device handle will match the MAC address of the trunk device created by iPXE, and the autoboot process will then erroneously consider the trunk device to be an autoboot device. Fix by recording the VLAN tag along with the MAC address, and treating the VLAN tag as part of the filter used to match the MAC address against candidate network devices. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_autoboot.h | 3 ++- src/include/usr/autoboot.h | 3 ++- src/interface/efi/efi_autoboot.c | 14 ++++++++++++-- src/interface/efi/efiprefix.c | 2 +- src/usr/autoboot.c | 16 +++++++++++++--- 5 files changed, 30 insertions(+), 8 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_autoboot.h b/src/include/ipxe/efi/efi_autoboot.h index 706885e28..94fd2d766 100644 --- a/src/include/ipxe/efi/efi_autoboot.h +++ b/src/include/ipxe/efi/efi_autoboot.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -extern int efi_set_autoboot_ll_addr ( EFI_HANDLE device ); +extern int efi_set_autoboot_ll_addr ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ); #endif /* _IPXE_EFI_AUTOBOOT_H */ diff --git a/src/include/usr/autoboot.h b/src/include/usr/autoboot.h index f88b8494f..3719b8243 100644 --- a/src/include/usr/autoboot.h +++ b/src/include/usr/autoboot.h @@ -28,7 +28,8 @@ enum uriboot_flags { extern void set_autoboot_busloc ( unsigned int bus_type, unsigned int location ); -extern void set_autoboot_ll_addr ( const void *ll_addr, size_t len ); +extern void set_autoboot_ll_addr ( const void *ll_addr, size_t len, + unsigned int vlan ); extern int uriboot ( struct uri *filename, struct uri **root_paths, unsigned int root_path_count, int drive, diff --git a/src/interface/efi/efi_autoboot.c b/src/interface/efi/efi_autoboot.c index 08d67f761..ec7793cd7 100644 --- a/src/interface/efi/efi_autoboot.c +++ b/src/interface/efi/efi_autoboot.c @@ -26,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -40,9 +41,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * Identify autoboot device * * @v device Device handle + * @v path Device path * @ret rc Return status code */ -int efi_set_autoboot_ll_addr ( EFI_HANDLE device ) { +int efi_set_autoboot_ll_addr ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; union { EFI_SIMPLE_NETWORK_PROTOCOL *snp; @@ -50,6 +53,7 @@ int efi_set_autoboot_ll_addr ( EFI_HANDLE device ) { } snp; EFI_SIMPLE_NETWORK_MODE *mode; EFI_STATUS efirc; + unsigned int vlan; int rc; /* Look for an SNP instance on the image's device handle */ @@ -66,10 +70,16 @@ int efi_set_autoboot_ll_addr ( EFI_HANDLE device ) { /* Record autoboot device */ mode = snp.snp->Mode; - set_autoboot_ll_addr ( &mode->CurrentAddress, mode->HwAddressSize ); + vlan = efi_path_vlan ( path ); + set_autoboot_ll_addr ( &mode->CurrentAddress, mode->HwAddressSize, + vlan ); DBGC ( device, "EFI %s found autoboot link-layer address:\n", efi_handle_name ( device ) ); DBGC_HDA ( device, 0, &mode->CurrentAddress, mode->HwAddressSize ); + if ( vlan ) { + DBGC ( device, "EFI %s found autoboot VLAN %d\n", + efi_handle_name ( device ), vlan ); + } /* Close protocol */ bs->CloseProtocol ( device, &efi_simple_network_protocol_guid, diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c index 7286b3e03..bdc36d9bd 100644 --- a/src/interface/efi/efiprefix.c +++ b/src/interface/efi/efiprefix.c @@ -81,7 +81,7 @@ static void efi_init_application ( void ) { EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path; /* Identify autoboot device, if any */ - efi_set_autoboot_ll_addr ( device ); + efi_set_autoboot_ll_addr ( device, path ); /* Store cached DHCP packet, if any */ efi_cachedhcp_record ( device, path ); diff --git a/src/usr/autoboot.c b/src/usr/autoboot.c index 24043ae69..d1f259621 100644 --- a/src/usr/autoboot.c +++ b/src/usr/autoboot.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -57,6 +58,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** Link-layer address of preferred autoboot device, if known */ static uint8_t autoboot_ll_addr[MAX_LL_ADDR_LEN]; +/** VLAN tag of preferred autoboot device, if known */ +static unsigned int autoboot_vlan; + /** Device location of preferred autoboot device, if known */ static struct device_description autoboot_desc; @@ -494,8 +498,9 @@ void set_autoboot_busloc ( unsigned int bus_type, unsigned int location ) { */ static int is_autoboot_ll_addr ( struct net_device *netdev ) { - return ( memcmp ( netdev->ll_addr, autoboot_ll_addr, - netdev->ll_protocol->ll_addr_len ) == 0 ); + return ( ( memcmp ( netdev->ll_addr, autoboot_ll_addr, + netdev->ll_protocol->ll_addr_len ) == 0 ) && + ( vlan_tag ( netdev ) == autoboot_vlan ) ); } /** @@ -503,14 +508,19 @@ static int is_autoboot_ll_addr ( struct net_device *netdev ) { * * @v ll_addr Link-layer address * @v len Length of link-layer address + * @v vlan VLAN tag */ -void set_autoboot_ll_addr ( const void *ll_addr, size_t len ) { +void set_autoboot_ll_addr ( const void *ll_addr, size_t len, + unsigned int vlan ) { /* Record autoboot link-layer address (truncated if necessary) */ if ( len > sizeof ( autoboot_ll_addr ) ) len = sizeof ( autoboot_ll_addr ); memcpy ( autoboot_ll_addr, ll_addr, len ); + /* Record autoboot VLAN tag */ + autoboot_vlan = vlan; + /* Mark autoboot device as present */ is_autoboot_device = is_autoboot_ll_addr; } -- cgit v1.2.3-55-g7522 From 2dcef4b7a11a780a684a69021a0c91bc43e03883 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 15 Jan 2023 22:42:30 +0000 Subject: [efi] Create VLAN autoboot device automatically When chainloading iPXE from an EFI VLAN device, configure the corresponding iPXE VLAN device to be created automatically. Signed-off-by: Michael Brown --- src/interface/efi/efi_autoboot.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_autoboot.c b/src/interface/efi/efi_autoboot.c index ec7793cd7..a103c2f19 100644 --- a/src/interface/efi/efi_autoboot.c +++ b/src/interface/efi/efi_autoboot.c @@ -25,6 +25,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include +#include #include #include #include @@ -81,6 +83,13 @@ int efi_set_autoboot_ll_addr ( EFI_HANDLE device, efi_handle_name ( device ), vlan ); } + /* Configure automatic VLAN device, if applicable */ + if ( vlan && ( mode->HwAddressSize == ETH_ALEN ) ) { + vlan_auto ( &mode->CurrentAddress, vlan ); + DBGC ( device, "EFI %s configured automatic VLAN %d\n", + efi_handle_name ( device ), vlan ); + } + /* Close protocol */ bs->CloseProtocol ( device, &efi_simple_network_protocol_guid, efi_image_handle, NULL ); -- cgit v1.2.3-55-g7522 From 204d39222a0ff9f91fdffc2809de0b7f5aaabbae Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 23 Jan 2023 19:07:35 +0000 Subject: [efi] Add efi_path_terminate() utility function Signed-off-by: Michael Brown --- src/image/efi_image.c | 4 +--- src/include/ipxe/efi/efi_path.h | 13 +++++++++++++ src/interface/efi/efi_block.c | 4 +--- src/interface/efi/efi_path.c | 32 ++++++++------------------------ src/interface/efi/efi_snp_hii.c | 4 +--- 5 files changed, 24 insertions(+), 33 deletions(-) (limited to 'src/interface') diff --git a/src/image/efi_image.c b/src/image/efi_image.c index 3c98decbf..66a19524b 100644 --- a/src/image/efi_image.c +++ b/src/image/efi_image.c @@ -96,9 +96,7 @@ efi_image_path ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) { efi_snprintf ( filepath->PathName, ( name_len + 1 /* NUL */ ), "%s", image->name ); end = ( ( ( void * ) filepath ) + filepath_len ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); return path; } diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 9dea74b5a..98b922ac1 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -21,6 +21,19 @@ struct fcp_description; struct ib_srp_device; struct usb_function; +/** + * Terminate device path + * + * @v end End of device path to fill in + */ +static inline void efi_path_terminate ( EFI_DEVICE_PATH_PROTOCOL *end ) { + + end->Type = END_DEVICE_PATH_TYPE; + end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; + end->Length[0] = sizeof ( *end ); + end->Length[1] = 0; +} + extern EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ); extern EFI_DEVICE_PATH_PROTOCOL * diff --git a/src/interface/efi/efi_block.c b/src/interface/efi/efi_block.c index 74cf7c0c0..cb73260d5 100644 --- a/src/interface/efi/efi_block.c +++ b/src/interface/efi/efi_block.c @@ -582,9 +582,7 @@ static int efi_block_boot_image ( struct san_device *sandev, EFI_HANDLE handle, sizeof ( efi_block_boot_filename ) ); } end = ( ( ( void * ) filepath ) + filepath_len ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); DBGC ( sandev, "EFIBLK %#02x trying to load %s\n", sandev->drive, efi_devpath_text ( boot_path ) ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index fb0f3059d..50027b75a 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -161,9 +161,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ) { } va_end ( args ); end = dst; - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); return path; } @@ -223,9 +221,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) { } else { end = ( ( ( void * ) macpath ) + sizeof ( *macpath ) ); } - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); return path; } @@ -265,9 +261,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_uri_path ( struct uri *uri ) { uripath->Header.Length[1] = ( uripath_len >> 8 ); format_uri ( uri, uripath->Uri, uri_len ); end = ( ( ( void * ) path ) + uripath_len ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); return path; } @@ -324,9 +318,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_iscsi_path ( struct iscsi_session *iscsi ) { name = ( ( ( void * ) iscsipath ) + sizeof ( *iscsipath ) ); memcpy ( name, iscsi->target_iqn, name_len ); end = ( ( ( void * ) name ) + name_len ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); /* Free temporary paths */ free ( netpath ); @@ -366,9 +358,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_aoe_path ( struct aoe_device *aoedev ) { satapath.sata.Header.Length[0] = sizeof ( satapath.sata ); satapath.sata.HBAPortNumber = aoedev->major; satapath.sata.PortMultiplierPortNumber = aoedev->minor; - satapath.end.Type = END_DEVICE_PATH_TYPE; - satapath.end.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - satapath.end.Length[0] = sizeof ( satapath.end ); + efi_path_terminate ( &satapath.end ); /* Construct overall device path */ path = efi_paths ( netpath, &satapath, NULL ); @@ -409,9 +399,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_fcp_path ( struct fcp_description *desc ) { path->fc.Header.Length[0] = sizeof ( path->fc ); memcpy ( path->fc.WWN, &desc->wwn, sizeof ( path->fc.WWN ) ); memcpy ( path->fc.Lun, &desc->lun, sizeof ( path->fc.Lun ) ); - path->end.Type = END_DEVICE_PATH_TYPE; - path->end.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - path->end.Length[0] = sizeof ( path->end ); + efi_path_terminate ( &path->end ); return &path->fc.Header; } @@ -463,9 +451,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) { memcpy ( &ibpath->DeviceId, &id->ib.id_ext, sizeof ( ibpath->DeviceId ) ); end = ( ( ( void * ) ibpath ) + sizeof ( *ibpath ) ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); return path; } @@ -511,9 +497,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) { /* Construct device path */ memcpy ( path, efidev->path, prefix_len ); end = ( ( ( void * ) path ) + len - sizeof ( *end ) ); - end->Type = END_DEVICE_PATH_TYPE; - end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - end->Length[0] = sizeof ( *end ); + efi_path_terminate ( end ); usbpath = ( ( ( void * ) end ) - sizeof ( *usbpath ) ); usbpath->InterfaceNumber = func->interface[0]; for ( ; usb ; usbpath--, usb = usb->port->hub->usb ) { diff --git a/src/interface/efi/efi_snp_hii.c b/src/interface/efi/efi_snp_hii.c index 5d5f80cd7..8b65c8a78 100644 --- a/src/interface/efi/efi_snp_hii.c +++ b/src/interface/efi/efi_snp_hii.c @@ -704,9 +704,7 @@ int efi_snp_hii_install ( struct efi_snp_device *snpdev ) { vendor_path->Header.Length[0] = sizeof ( *vendor_path ); efi_snp_hii_random_guid ( &vendor_path->Guid ); path_end = ( ( void * ) ( vendor_path + 1 ) ); - path_end->Type = END_DEVICE_PATH_TYPE; - path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; - path_end->Length[0] = sizeof ( *path_end ); + efi_path_terminate ( path_end ); /* Create device path and child handle for HII association */ if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( -- cgit v1.2.3-55-g7522 From 1cd0a248cc54a8b2fadc0d2c287d2f3528b749b4 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 23 Jan 2023 19:12:49 +0000 Subject: [efi] Add efi_path_prev() utility function Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_path.h | 3 +++ src/interface/efi/efi_path.c | 27 ++++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 98b922ac1..e75ae42c4 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -37,6 +37,9 @@ static inline void efi_path_terminate ( EFI_DEVICE_PATH_PROTOCOL *end ) { extern EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ); extern EFI_DEVICE_PATH_PROTOCOL * +efi_path_prev ( EFI_DEVICE_PATH_PROTOCOL *path, + EFI_DEVICE_PATH_PROTOCOL *curr ); +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 unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 50027b75a..b28ecddbb 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -65,6 +65,25 @@ EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ) { return path; } +/** + * Find previous element of device path + * + * @v path Device path, or NULL for no path + * @v curr Current element in device path, or NULL for end of path + * @ret prev Previous element in device path, or NULL + */ +EFI_DEVICE_PATH_PROTOCOL * efi_path_prev ( EFI_DEVICE_PATH_PROTOCOL *path, + EFI_DEVICE_PATH_PROTOCOL *curr ) { + EFI_DEVICE_PATH_PROTOCOL *tmp; + + /* Find immediately preceding element */ + while ( ( tmp = efi_path_next ( path ) ) != curr ) { + path = tmp; + } + + return path; +} + /** * Find end of device path * @@ -72,14 +91,8 @@ EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ) { * @ret path_end End of device path, or NULL */ EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { - EFI_DEVICE_PATH_PROTOCOL *next; - /* Find end of device path */ - while ( ( next = efi_path_next ( path ) ) != NULL ) { - path = next; - } - - return path; + return efi_path_prev ( path, NULL ); } /** -- cgit v1.2.3-55-g7522 From 2fef0c541e4e2417fc285c4d9ddfcb6f23f394ad Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 23 Jan 2023 19:15:45 +0000 Subject: [efi] Extend efi_locate_device() to allow searching up the device path Extend the functionality of efi_locate_device() to allow callers to find instances of the protocol that may exist further up the device path. Signed-off-by: Michael Brown --- src/drivers/net/efi/nii.c | 2 +- src/drivers/net/efi/snponly.c | 2 +- src/include/ipxe/efi/efi_utils.h | 2 +- src/interface/efi/efi_utils.c | 56 ++++++++++++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 17 deletions(-) (limited to 'src/interface') diff --git a/src/drivers/net/efi/nii.c b/src/drivers/net/efi/nii.c index 5d9aea8d5..be5bce4b4 100644 --- a/src/drivers/net/efi/nii.c +++ b/src/drivers/net/efi/nii.c @@ -222,7 +222,7 @@ static int nii_pci_open ( struct nii_nic *nii ) { /* Locate PCI I/O protocol */ if ( ( rc = efi_locate_device ( device, &efi_pci_io_protocol_guid, - &pci_device ) ) != 0 ) { + &pci_device, 0 ) ) != 0 ) { DBGC ( nii, "NII %s could not locate PCI I/O protocol: %s\n", nii->dev.name, strerror ( rc ) ); goto err_locate; diff --git a/src/drivers/net/efi/snponly.c b/src/drivers/net/efi/snponly.c index cb7ea1bbc..674e0a050 100644 --- a/src/drivers/net/efi/snponly.c +++ b/src/drivers/net/efi/snponly.c @@ -80,7 +80,7 @@ static int chained_locate ( struct chained_protocol *chained ) { /* Locate handle supporting this protocol */ if ( ( rc = efi_locate_device ( device, chained->protocol, - &parent ) ) != 0 ) { + &parent, 0 ) ) != 0 ) { DBGC ( device, "CHAINED %s does not support %s: %s\n", efi_handle_name ( device ), efi_guid_ntoa ( chained->protocol ), strerror ( rc ) ); diff --git a/src/include/ipxe/efi/efi_utils.h b/src/include/ipxe/efi/efi_utils.h index 270d38dc8..98659b150 100644 --- a/src/include/ipxe/efi/efi_utils.h +++ b/src/include/ipxe/efi/efi_utils.h @@ -13,7 +13,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct device; extern int efi_locate_device ( EFI_HANDLE device, EFI_GUID *protocol, - EFI_HANDLE *parent ); + EFI_HANDLE *parent, unsigned int skip ); extern int efi_child_add ( EFI_HANDLE parent, EFI_HANDLE child ); extern void efi_child_del ( EFI_HANDLE parent, EFI_HANDLE child ); extern void efi_device_info ( EFI_HANDLE device, const char *prefix, diff --git a/src/interface/efi/efi_utils.c b/src/interface/efi/efi_utils.c index 8e660e9d7..53f82bfe4 100644 --- a/src/interface/efi/efi_utils.c +++ b/src/interface/efi/efi_utils.c @@ -23,6 +23,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include @@ -38,23 +39,26 @@ FILE_LICENCE ( GPL2_OR_LATER ); * @v device EFI device handle * @v protocol Protocol GUID * @v parent Parent EFI device handle to fill in + * @v skip Number of protocol-supporting parent devices to skip * @ret rc Return status code */ int efi_locate_device ( EFI_HANDLE device, EFI_GUID *protocol, - EFI_HANDLE *parent ) { + EFI_HANDLE *parent, unsigned int skip ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; union { EFI_DEVICE_PATH_PROTOCOL *path; void *interface; - } path; - EFI_DEVICE_PATH_PROTOCOL *devpath; + } u; + EFI_DEVICE_PATH_PROTOCOL *path; + EFI_DEVICE_PATH_PROTOCOL *end; + size_t len; EFI_STATUS efirc; int rc; /* Get device path */ if ( ( efirc = bs->OpenProtocol ( device, &efi_device_path_protocol_guid, - &path.interface, + &u.interface, efi_image_handle, device, EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ rc = -EEFI ( efirc ); @@ -62,22 +66,46 @@ int efi_locate_device ( EFI_HANDLE device, EFI_GUID *protocol, efi_handle_name ( device ), strerror ( rc ) ); goto err_open_device_path; } - devpath = path.path; - /* Check for presence of specified protocol */ - if ( ( efirc = bs->LocateDevicePath ( protocol, &devpath, - parent ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFIDEV %s has no parent supporting %s: %s\n", - efi_handle_name ( device ), - efi_guid_ntoa ( protocol ), strerror ( rc ) ); - goto err_locate_protocol; + /* Create modifiable copy of device path */ + len = ( efi_path_len ( u.path ) + sizeof ( EFI_DEVICE_PATH_PROTOCOL )); + path = malloc ( len ); + if ( ! path ) { + rc = -ENOMEM; + goto err_alloc_path; + } + memcpy ( path, u.path, len ); + + /* Locate parent device(s) */ + while ( 1 ) { + + /* Check for presence of specified protocol */ + end = path; + if ( ( efirc = bs->LocateDevicePath ( protocol, &end, + parent ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIDEV %s has no parent supporting " + "%s: %s\n", efi_devpath_text ( path ), + efi_guid_ntoa ( protocol ), strerror ( rc ) ); + goto err_locate_protocol; + } + + /* Stop if we have skipped the requested number of devices */ + if ( ! skip-- ) + break; + + /* Trim device path */ + efi_path_terminate ( end ); + end = efi_path_prev ( path, end ); + efi_path_terminate ( end ); } /* Success */ rc = 0; err_locate_protocol: + free ( path ); + err_alloc_path: bs->CloseProtocol ( device, &efi_device_path_protocol_guid, efi_image_handle, device ); err_open_device_path: @@ -150,7 +178,7 @@ static int efi_pci_info ( EFI_HANDLE device, const char *prefix, /* Find parent PCI device */ if ( ( rc = efi_locate_device ( device, &efi_pci_io_protocol_guid, - &pci_device ) ) != 0 ) { + &pci_device, 0 ) ) != 0 ) { DBGC ( device, "EFIDEV %s is not a PCI device: %s\n", efi_handle_name ( device ), strerror ( rc ) ); return rc; -- cgit v1.2.3-55-g7522 From 5220bdc5242877d8d6d457b5f4f6f5f3da78a833 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sat, 28 Jan 2023 17:15:16 +0000 Subject: [legal] Add missing FILE_LICENCE declaration to efi_path.c Signed-off-by: Michael Brown --- src/interface/efi/efi_path.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index b28ecddbb..a78f97fce 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -17,6 +17,8 @@ * 02110-1301, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include #include #include -- cgit v1.2.3-55-g7522 From 4bb521a8c4b324902651714915dfe6fd4a5c36af Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 29 Jan 2023 18:48:08 +0000 Subject: [efi] Accept a command line passed to an iPXE image via LoadOptions Treat a command line passed to iPXE via UEFI LoadOptions as an image to be registered at startup, as is already done for the .lkrn, .pxe, and .exe BIOS images. Originally-implemented-by: Ladi Prosek Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_cmdline.h | 18 +++++ src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_cmdline.c | 151 +++++++++++++++++++++++++++++++++++++ src/interface/efi/efi_init.c | 5 ++ 4 files changed, 175 insertions(+) create mode 100644 src/include/ipxe/efi/efi_cmdline.h create mode 100644 src/interface/efi/efi_cmdline.c (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_cmdline.h b/src/include/ipxe/efi/efi_cmdline.h new file mode 100644 index 000000000..45abd5493 --- /dev/null +++ b/src/include/ipxe/efi/efi_cmdline.h @@ -0,0 +1,18 @@ +#ifndef _IPXE_EFI_CMDLINE_H +#define _IPXE_EFI_CMDLINE_H + +/** @file + * + * EFI command line + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +extern const wchar_t *efi_cmdline; +extern size_t efi_cmdline_len; + +#endif /* _IPXE_EFI_CMDLINE_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index c3541e8a0..7c3b0c43b 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -401,6 +401,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_dynkeymap ( ERRFILE_OTHER | 0x00580000 ) #define ERRFILE_pci_cmd ( ERRFILE_OTHER | 0x00590000 ) #define ERRFILE_dhe ( ERRFILE_OTHER | 0x005a0000 ) +#define ERRFILE_efi_cmdline ( ERRFILE_OTHER | 0x005b0000 ) /** @} */ diff --git a/src/interface/efi/efi_cmdline.c b/src/interface/efi/efi_cmdline.c new file mode 100644 index 000000000..b33bebd8c --- /dev/null +++ b/src/interface/efi/efi_cmdline.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * EFI command line + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** EFI command line (may not be wNUL-terminated */ +const wchar_t *efi_cmdline; + +/** Length of EFI command line (in bytes) */ +size_t efi_cmdline_len; + +/** Internal copy of the command line */ +static char *efi_cmdline_copy; + +/** + * Free command line image + * + * @v refcnt Reference count + */ +static void efi_cmdline_free ( struct refcnt *refcnt ) { + struct image *image = container_of ( refcnt, struct image, refcnt ); + + DBGC ( image, "CMDLINE freeing command line\n" ); + free ( efi_cmdline_copy ); +} + +/** Embedded script representing the command line */ +static struct image efi_cmdline_image = { + .refcnt = REF_INIT ( efi_cmdline_free ), + .name = "", + .type = &script_image_type, +}; + +/** Colour for debug messages */ +#define colour &efi_cmdline_image + +/** + * Initialise EFI command line + * + * @ret rc Return status code + */ +static int efi_cmdline_init ( void ) { + char *cmdline; + size_t len; + int rc; + + /* Do nothing if no command line was specified */ + if ( ! efi_cmdline_len ) { + DBGC ( colour, "CMDLINE found no command line\n" ); + return 0; + } + + /* Allocate ASCII copy of command line */ + len = ( ( efi_cmdline_len / sizeof ( efi_cmdline[0] ) ) + 1 /* NUL */ ); + efi_cmdline_copy = malloc ( len ); + if ( ! efi_cmdline_copy ) { + rc = -ENOMEM; + goto err_alloc; + } + cmdline = efi_cmdline_copy; + snprintf ( cmdline, len, "%ls", efi_cmdline ); + DBGC ( colour, "CMDLINE found command line \"%s\"\n", cmdline ); + + /* Mark command line as consumed */ + efi_cmdline_len = 0; + + /* Strip image name and surrounding whitespace */ + while ( isspace ( *cmdline ) ) + cmdline++; + while ( *cmdline && ( ! isspace ( *cmdline ) ) ) + cmdline++; + while ( isspace ( *cmdline ) ) + cmdline++; + DBGC ( colour, "CMDLINE using command line \"%s\"\n", cmdline ); + + /* Prepare and register image */ + efi_cmdline_image.data = virt_to_user ( cmdline ); + efi_cmdline_image.len = strlen ( cmdline ); + if ( efi_cmdline_image.len && + ( ( rc = register_image ( &efi_cmdline_image ) ) != 0 ) ) { + DBGC ( colour, "CMDLINE could not register command line: %s\n", + strerror ( rc ) ); + goto err_register_image; + } + + /* Drop our reference to the image */ + image_put ( &efi_cmdline_image ); + + return 0; + + err_register_image: + image_put ( &efi_cmdline_image ); + err_alloc: + return rc; +} + +/** + * EFI command line startup function + * + */ +static void efi_cmdline_startup ( void ) { + int rc; + + /* Initialise command line */ + if ( ( rc = efi_cmdline_init() ) != 0 ) { + /* No way to report failure */ + return; + } +} + +/** Command line and initrd initialisation function */ +struct startup_fn efi_cmdline_startup_fn __startup_fn ( STARTUP_NORMAL ) = { + .name = "efi_cmdline", + .startup = efi_cmdline_startup, +}; diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c index 5d98f9ff7..d3c5042d7 100644 --- a/src/interface/efi/efi_init.c +++ b/src/interface/efi/efi_init.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include /** Image handle passed to entry point */ @@ -254,6 +255,10 @@ EFI_STATUS efi_init ( EFI_HANDLE image_handle, DBGC ( systab, "EFI image base address %p\n", efi_loaded_image->ImageBase ); + /* Record command line */ + efi_cmdline = efi_loaded_image->LoadOptions; + efi_cmdline_len = efi_loaded_image->LoadOptionsSize; + /* Get loaded image's device handle's device path */ if ( ( efirc = bs->OpenProtocol ( efi_loaded_image->DeviceHandle, &efi_device_path_protocol_guid, -- cgit v1.2.3-55-g7522 From 6f250be279311d461f78bb17eb8b5b70ad90dd0a Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 1 Feb 2023 23:54:19 +0000 Subject: [efi] Allow autoexec script to be located alongside iPXE binary Try loading the autoexec.ipxe script first from the directory containing the iPXE binary (based on the relative file path provided to us via EFI_LOADED_IMAGE_PROTOCOL), then fall back to trying the root directory. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_autoexec.h | 3 +- src/interface/efi/efi_autoexec.c | 85 +++++++++++++++++++++++++++++-------- src/interface/efi/efiprefix.c | 9 ++-- 3 files changed, 75 insertions(+), 22 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_autoexec.h b/src/include/ipxe/efi/efi_autoexec.h index 1f93b41cd..08ddf5836 100644 --- a/src/include/ipxe/efi/efi_autoexec.h +++ b/src/include/ipxe/efi/efi_autoexec.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -extern int efi_autoexec_load ( EFI_HANDLE device ); +extern int efi_autoexec_load ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ); #endif /* _IPXE_EFI_AUTOEXEC_H */ diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index 79d4a4caf..fb12cef08 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -54,12 +54,14 @@ static void *efi_autoexec; static size_t efi_autoexec_len; /** - * Load autoexec script from filesystem + * Load autoexec script from path within filesystem * * @v device Device handle + * @v path Relative path to image, or NULL to load from root * @ret rc Return status code */ -static int efi_autoexec_filesystem ( EFI_HANDLE device ) { +static int efi_autoexec_filesystem ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; union { void *interface; @@ -70,13 +72,58 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { CHAR16 name[ sizeof ( efi_autoexec_wname ) / sizeof ( efi_autoexec_wname[0] ) ]; } info; + FILEPATH_DEVICE_PATH *filepath; EFI_FILE_PROTOCOL *root; EFI_FILE_PROTOCOL *file; UINTN size; VOID *data; + unsigned int dirlen; + size_t len; + CHAR16 *wname; EFI_STATUS efirc; int rc; + /* Identify directory */ + if ( path ) { + + /* Check relative device path is a file path */ + if ( ! ( ( path->Type == MEDIA_DEVICE_PATH ) && + ( path->SubType == MEDIA_FILEPATH_DP ) ) ) { + DBGC ( device, "EFI %s image path ", + efi_handle_name ( device ) ); + DBGC ( device, " \"%s\" is not a file path\n", + efi_devpath_text ( path ) ); + rc = -ENOTTY; + goto err_not_filepath; + } + filepath = container_of ( path, FILEPATH_DEVICE_PATH, Header ); + + /* Find length of containing directory */ + dirlen = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] ) + - offsetof ( typeof ( *filepath ), PathName ) ) + / sizeof ( filepath->PathName[0] ) ); + for ( ; dirlen ; dirlen-- ) { + if ( filepath->PathName[ dirlen - 1 ] == L'\\' ) + break; + } + + } else { + + /* Use root directory */ + filepath = NULL; + dirlen = 0; + } + + /* Allocate filename */ + len = ( ( dirlen * sizeof ( wname[0] ) ) + sizeof ( efi_autoexec_wname ) ); + wname = malloc ( len ); + if ( ! wname ) { + rc = -ENOMEM; + goto err_wname; + } + memcpy ( wname, filepath->PathName, ( dirlen * sizeof ( wname[0] ) ) ); + memcpy ( &wname[dirlen], efi_autoexec_wname, sizeof ( efi_autoexec_wname ) ); + /* Open simple file system protocol */ if ( ( efirc = bs->OpenProtocol ( device, &efi_simple_file_system_protocol_guid, @@ -98,12 +145,11 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { } /* Open autoexec script */ - if ( ( efirc = root->Open ( root, &file, efi_autoexec_wname, + if ( ( efirc = root->Open ( root, &file, wname, EFI_FILE_MODE_READ, 0 ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s has no %ls: %s\n", - efi_handle_name ( device ), efi_autoexec_wname, - strerror ( rc ) ); + efi_handle_name ( device ), wname, strerror ( rc ) ); goto err_open; } @@ -113,8 +159,7 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { &info ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not get %ls info: %s\n", - efi_handle_name ( device ), efi_autoexec_wname, - strerror ( rc ) ); + efi_handle_name ( device ), wname, strerror ( rc ) ); goto err_getinfo; } size = info.info.FileSize; @@ -123,7 +168,7 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { if ( ! size ) { rc = -EINVAL; DBGC ( device, "EFI %s has zero-length %ls\n", - efi_handle_name ( device ), efi_autoexec_wname ); + efi_handle_name ( device ), wname ); goto err_empty; } @@ -132,8 +177,7 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { &data ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not allocate %ls: %s\n", - efi_handle_name ( device ), efi_autoexec_wname, - strerror ( rc ) ); + efi_handle_name ( device ), wname, strerror ( rc ) ); goto err_alloc; } @@ -141,8 +185,7 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not read %ls: %s\n", - efi_handle_name ( device ), efi_autoexec_wname, - strerror ( rc ) ); + efi_handle_name ( device ), wname, strerror ( rc ) ); goto err_read; } @@ -150,8 +193,7 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { efi_autoexec = data; efi_autoexec_len = size; data = NULL; - DBGC ( device, "EFI %s found %ls\n", - efi_handle_name ( device ), efi_autoexec_wname ); + DBGC ( device, "EFI %s found %ls\n", efi_handle_name ( device ), wname ); /* Success */ rc = 0; @@ -169,6 +211,9 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { bs->CloseProtocol ( device, &efi_simple_file_system_protocol_guid, efi_image_handle, device ); err_filesystem: + free ( wname ); + err_wname: + err_not_filepath: return rc; } @@ -345,17 +390,23 @@ static int efi_autoexec_tftp ( EFI_HANDLE device ) { * Load autoexec script * * @v device Device handle + * @v path Image path within device handle * @ret rc Return status code */ -int efi_autoexec_load ( EFI_HANDLE device ) { +int efi_autoexec_load ( EFI_HANDLE device, + EFI_DEVICE_PATH_PROTOCOL *path ) { int rc; /* Sanity check */ assert ( efi_autoexec == NULL ); assert ( efi_autoexec_len == 0 ); - /* Try loading from file system, if supported */ - if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 ) + /* Try loading from file system loaded image directory, if supported */ + if ( ( rc = efi_autoexec_filesystem ( device, path ) ) == 0 ) + return 0; + + /* Try loading from file system root directory, if supported */ + if ( ( rc = efi_autoexec_filesystem ( device, NULL ) ) == 0 ) return 0; /* Try loading via TFTP, if supported */ diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c index bdc36d9bd..261160681 100644 --- a/src/interface/efi/efiprefix.c +++ b/src/interface/efi/efiprefix.c @@ -78,16 +78,17 @@ EFI_STATUS EFIAPI _efi_start ( EFI_HANDLE image_handle, */ static void efi_init_application ( void ) { EFI_HANDLE device = efi_loaded_image->DeviceHandle; - EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path; + EFI_DEVICE_PATH_PROTOCOL *devpath = efi_loaded_image_path; + EFI_DEVICE_PATH_PROTOCOL *filepath = efi_loaded_image->FilePath; /* Identify autoboot device, if any */ - efi_set_autoboot_ll_addr ( device, path ); + efi_set_autoboot_ll_addr ( device, devpath ); /* Store cached DHCP packet, if any */ - efi_cachedhcp_record ( device, path ); + efi_cachedhcp_record ( device, devpath ); /* Load autoexec script, if any */ - efi_autoexec_load ( device ); + efi_autoexec_load ( device, filepath ); } /** EFI application initialisation function */ -- cgit v1.2.3-55-g7522 From 4e456d992889569e2dbb0426f2438797ab06ca1f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 3 Feb 2023 16:10:31 +0000 Subject: [efi] Do not attempt to drive PCI bridge devices The "bridge" driver introduced in 3aa6b79 ("[pci] Add minimal PCI bridge driver") is required only for BIOS builds using the ENA driver, where experimentation shows that we cannot rely on the BIOS to fully assign MMIO addresses. Since the driver is a valid PCI driver, it will end up binding to all PCI bridge devices even on a UEFI platform, where the firmware is likely to have completed MMIO address assignment correctly. This has no impact on most systems since there is generally no UEFI driver for PCI bridges: the enumeration of the whole PCI bus is handled by the PciBusDxe driver bound to the root bridge. Experimentation shows that at least one laptop will freeze at the point that iPXE attempts to bind to the bridge device. No deeper investigation has been carried out to find the root cause. Fix by causing efipci_supported() to return an error unless the configuration space header type indicates a non-bridge device. Reported-by: Marcel Petersen Signed-off-by: Michael Brown --- src/drivers/bus/pci.c | 1 + src/include/ipxe/pci.h | 2 ++ src/interface/efi/efi_pci.c | 10 ++++++++++ 3 files changed, 13 insertions(+) (limited to 'src/interface') diff --git a/src/drivers/bus/pci.c b/src/drivers/bus/pci.c index 7953aaedd..92b389641 100644 --- a/src/drivers/bus/pci.c +++ b/src/drivers/bus/pci.c @@ -205,6 +205,7 @@ int pci_read_config ( struct pci_device *pci ) { pci_read_config_dword ( pci, PCI_REVISION, &tmp ); pci->class = ( tmp >> 8 ); pci_read_config_byte ( pci, PCI_INTERRUPT_LINE, &pci->irq ); + pci_read_config_byte ( pci, PCI_HEADER_TYPE, &pci->hdrtype ); pci_read_bases ( pci ); /* Initialise generic device component */ diff --git a/src/include/ipxe/pci.h b/src/include/ipxe/pci.h index 637b20d60..8c6d9e4e2 100644 --- a/src/include/ipxe/pci.h +++ b/src/include/ipxe/pci.h @@ -227,6 +227,8 @@ struct pci_device { uint32_t class; /** Interrupt number */ uint8_t irq; + /** Header type */ + uint8_t hdrtype; /** Segment, bus, device, and function (bus:dev.fn) number */ uint32_t busdevfn; /** Driver for this device */ diff --git a/src/interface/efi/efi_pci.c b/src/interface/efi/efi_pci.c index 4796201e9..e2eeeb344 100644 --- a/src/interface/efi/efi_pci.c +++ b/src/interface/efi/efi_pci.c @@ -785,12 +785,22 @@ int efipci_info ( EFI_HANDLE device, struct efi_pci_device *efipci ) { */ static int efipci_supported ( EFI_HANDLE device ) { struct efi_pci_device efipci; + uint8_t hdrtype; int rc; /* Get PCI device information */ if ( ( rc = efipci_info ( device, &efipci ) ) != 0 ) return rc; + /* Do not attempt to drive bridges */ + hdrtype = efipci.pci.hdrtype; + if ( ( hdrtype & PCI_HEADER_TYPE_MASK ) != PCI_HEADER_TYPE_NORMAL ) { + DBGC ( device, "EFIPCI " PCI_FMT " type %02x is not type %02x\n", + PCI_ARGS ( &efipci.pci ), hdrtype, + PCI_HEADER_TYPE_NORMAL ); + return -ENOTTY; + } + /* Look for a driver */ if ( ( rc = pci_find_driver ( &efipci.pci ) ) != 0 ) { DBGC ( device, "EFIPCI " PCI_FMT " (%04x:%04x class %06x) " -- cgit v1.2.3-55-g7522 From cf9ad00afcd6d8873fbefbbaf1f0813948a796ba Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 15 Feb 2023 17:17:43 +0000 Subject: [efi] Fix debug message when reading from EFI virtual files Show the requested range when a caller reads from a virtual file via the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL interface. Signed-off-by: Michael Brown --- src/interface/efi/efi_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index fc64b369c..7ed3ea5ac 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -528,7 +528,7 @@ static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this, /* Read from the file */ DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n", - efi_file_name ( file ), pos, file->pos ); + efi_file_name ( file ), pos, ( ( size_t ) ( pos + *len ) ) ); *len = file->read ( &reader ); assert ( ( pos + *len ) == file->pos ); -- cgit v1.2.3-55-g7522 From 6a004be0cceab5d669eedb5a2e6ee2feab31d5bd Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 15 Feb 2023 15:48:31 +0000 Subject: [efi] Support the initrd autodetection mechanism in newer Linux kernels Linux 5.7 added the ability to autodetect an initrd by searching for a handle via a fixed vendor-specific "Linux initrd device path" and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on that handle. This maps quite naturally onto our existing concept of a "magic initrd" as introduced for EFI in commit e5f0255 ("[efi] Provide an "initrd.magic" file for use by UEFI kernels"). Add an EFI_LOAD_FILE2_PROTOCOL instance to our EFI virtual files (backed by simply calling the existing EFI_SIMPLE_FILE_SYSTEM_PROTOCOL method to read from the file), and install the protocol instance for the "initrd.magic" virtual file onto a new device handle that also provides the Linux initrd device path. The design choice in Linux of using a single fixed device path makes this unfortunately messy to support, since device paths must be unique within a system. When multiple bootloaders are used (e.g. GRUB loading iPXE loading Linux) then only one bootloader can ever install the device path onto a handle. Subsequent bootloaders must locate the existing handle and replace the load file protocol instance with their own. Signed-off-by: Michael Brown --- src/interface/efi/efi_file.c | 190 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index 7ed3ea5ac..e8debbbe1 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -43,14 +43,21 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include +#include #include /** EFI media ID */ #define EFI_MEDIA_ID_MAGIC 0x69505845 +/** Linux initrd fixed device path vendor GUID */ +#define LINUX_INITRD_VENDOR_GUID \ + { 0x5568e427, 0x68fc, 0x4f3d, \ + { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } } + /** An EFI virtual file reader */ struct efi_file_reader { /** EFI file */ @@ -69,6 +76,8 @@ struct efi_file { struct refcnt refcnt; /** EFI file protocol */ EFI_FILE_PROTOCOL file; + /** EFI load file protocol */ + EFI_LOAD_FILE2_PROTOCOL load; /** Image (if any) */ struct image *image; /** Filename */ @@ -370,6 +379,8 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, ref_init ( &file->refcnt, efi_file_free ); memcpy ( &new_file->file, &efi_file_root.file, sizeof ( new_file->file ) ); + memcpy ( &new_file->load, &efi_file_root.load, + sizeof ( new_file->load ) ); efi_file_image ( new_file, image_get ( image ) ); *new = &new_file->file; DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) ); @@ -684,6 +695,44 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) { return 0; } +/** + * Load file + * + * @v this EFI file loader + * @v path File path + * @v boot Boot policy + * @v len Buffer size + * @v data Buffer, or NULL + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_load ( EFI_LOAD_FILE2_PROTOCOL *this, + EFI_DEVICE_PATH_PROTOCOL *path __unused, + BOOLEAN boot __unused, UINTN *len, + VOID *data ) { + struct efi_file *file = container_of ( this, struct efi_file, load ); + size_t max_len; + size_t file_len; + EFI_STATUS efirc; + + /* Calculate maximum length */ + max_len = ( data ? *len : 0 ); + DBGC ( file, "EFIFILE %s load at %p+%#zx\n", + efi_file_name ( file ), data, max_len ); + + /* Check buffer size */ + file_len = efi_file_len ( file ); + if ( file_len > max_len ) { + *len = file_len; + return EFI_BUFFER_TOO_SMALL; + } + + /* Read from file */ + if ( ( efirc = efi_file_read ( &file->file, len, data ) ) != 0 ) + return efirc; + + return 0; +} + /** Root directory */ static struct efi_file efi_file_root = { .refcnt = REF_INIT ( ref_no_free ), @@ -700,6 +749,9 @@ static struct efi_file efi_file_root = { .SetInfo = efi_file_set_info, .Flush = efi_file_flush, }, + .load = { + .LoadFile = efi_file_load, + }, .image = NULL, .name = "", }; @@ -720,11 +772,34 @@ static struct efi_file efi_file_initrd = { .SetInfo = efi_file_set_info, .Flush = efi_file_flush, }, + .load = { + .LoadFile = efi_file_load, + }, .image = NULL, .name = "initrd.magic", .read = efi_file_read_initrd, }; +/** Linux initrd fixed device path */ +static struct { + VENDOR_DEVICE_PATH vendor; + EFI_DEVICE_PATH_PROTOCOL end; +} __attribute__ (( packed )) efi_file_initrd_path = { + .vendor = { + .Header = { + .Type = MEDIA_DEVICE_PATH, + .SubType = MEDIA_VENDOR_DP, + .Length[0] = sizeof ( efi_file_initrd_path.vendor ), + }, + .Guid = LINUX_INITRD_VENDOR_GUID, + }, + .end = { + .Type = END_DEVICE_PATH_TYPE, + .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, + .Length[0] = sizeof ( efi_file_initrd_path.end ), + }, +}; + /** * Open root directory * @@ -833,6 +908,110 @@ static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = { .WriteDisk = efi_disk_io_write_disk, }; +/** + * (Re)install fixed device path file + * + * @v path Device path + * @v load Load file protocol, or NULL to uninstall protocol + * @ret rc Return status code + * + * Linux 5.7 added the ability to autodetect an initrd by searching + * for a handle via a fixed vendor-specific "Linux initrd device path" + * and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on + * that handle. + * + * The design choice in Linux of using a single fixed device path + * makes this unfortunately messy to support, since device paths must + * be unique within a system. When multiple bootloaders are used + * (e.g. GRUB loading iPXE loading Linux) then only one bootloader can + * ever install the device path onto a handle. Subsequent bootloaders + * must locate the existing handle and replace the load file protocol + * instance with their own. + */ +static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path, + EFI_LOAD_FILE2_PROTOCOL *load ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_DEVICE_PATH_PROTOCOL *end; + EFI_HANDLE handle; + VOID *path_copy; + VOID *old; + size_t path_len; + EFI_STATUS efirc; + int rc; + + /* Locate or install the handle with this device path */ + end = path; + if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid, + &end, &handle ) ) == 0 ) && + ( end->Type == END_DEVICE_PATH_TYPE ) ) { + + /* Exact match: reuse (or uninstall from) this handle */ + if ( load ) { + DBGC ( path, "EFIFILE %s reusing existing handle\n", + efi_devpath_text ( path ) ); + } + + } else { + + /* Allocate a permanent copy of the device path, since + * this handle will survive after this binary is + * unloaded. + */ + path_len = ( efi_path_len ( path ) + sizeof ( *end ) ); + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, path_len, + &path_copy ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( path, "EFIFILE %s could not allocate device path: " + "%s\n", efi_devpath_text ( path ), strerror ( rc ) ); + return rc; + } + memcpy ( path_copy, path, path_len ); + + /* Create a new handle with this device path */ + handle = NULL; + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, + &efi_device_path_protocol_guid, path_copy, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( path, "EFIFILE %s could not create handle: %s\n", + efi_devpath_text ( path ), strerror ( rc ) ); + return rc; + } + } + + /* Uninstall existing load file protocol instance, if any */ + if ( ( ( efirc = bs->HandleProtocol ( handle, &efi_load_file2_protocol_guid, + &old ) ) == 0 ) && + ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_load_file2_protocol_guid, old, + NULL ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( path, "EFIFILE %s could not uninstall %s: %s\n", + efi_devpath_text ( path ), + efi_guid_ntoa ( &efi_load_file2_protocol_guid ), + strerror ( rc ) ); + return rc; + } + + /* Install new load file protocol instance, if applicable */ + if ( ( load != NULL ) && + ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, + &efi_load_file2_protocol_guid, load, + NULL ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( path, "EFIFILE %s could not install %s: %s\n", + efi_devpath_text ( path ), + efi_guid_ntoa ( &efi_load_file2_protocol_guid ), + strerror ( rc ) ); + return rc; + } + + return 0; +} + /** * Install EFI simple file system protocol * @@ -903,8 +1082,16 @@ int efi_file_install ( EFI_HANDLE handle ) { } assert ( diskio.diskio == &efi_disk_io_protocol ); + /* Install Linux initrd fixed device path file */ + if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header, + &efi_file_initrd.load ) ) != 0 ) { + goto err_initrd; + } + return 0; + efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); + err_initrd: bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, efi_image_handle, handle ); err_open: @@ -930,6 +1117,9 @@ void efi_file_uninstall ( EFI_HANDLE handle ) { EFI_STATUS efirc; int rc; + /* Uninstall Linux initrd fixed device path file */ + efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); + /* Close our own disk I/O protocol */ bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, efi_image_handle, handle ); -- cgit v1.2.3-55-g7522 From 9f17d1116d27696ec76c48c5c77df34cba521380 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 17 Feb 2023 16:56:11 +0000 Subject: [rng] Allow entropy source to be selected at runtime As noted in commit 3c83843 ("[rng] Check for several functioning RTC interrupts"), experimentation shows that Hyper-V cannot be trusted to reliably generate RTC interrupts. (As noted in commit f3ba0fb ("[hyperv] Provide timer based on the 10MHz time reference count MSR"), Hyper-V appears to suffer from a general problem in reliably generating any legacy interrupts.) An alternative entropy source is therefore required for an image that may be used in a Hyper-V Gen1 virtual machine. The x86 RDRAND instruction provides a suitable alternative entropy source, but may not be supported by all CPUs. We must therefore allow for multiple entropy sources to be compiled in, with the single active entropy source selected only at runtime. Restructure the internal entropy API to allow a working entropy source to be detected and chosen at runtime. Enable the RDRAND entropy source for all x86 builds, since it is likely to be substantially faster than any other source. Signed-off-by: Michael Brown --- src/arch/arm/include/bits/entropy.h | 12 - src/arch/loong64/include/bits/entropy.h | 12 - src/arch/x86/core/rdrand.c | 32 ++- src/arch/x86/include/bits/entropy.h | 15 - src/arch/x86/include/ipxe/rdrand.h | 37 --- src/arch/x86/include/ipxe/rtc_entropy.h | 62 ----- src/arch/x86/interface/pcbios/rtc_entropy.c | 38 ++- src/config/config_entropy.c | 48 ++++ src/config/defaults/efi.h | 1 + src/config/defaults/linux.h | 4 + src/config/defaults/pcbios.h | 1 + src/crypto/entropy.c | 283 +++++++------------ src/crypto/null_entropy.c | 40 --- src/include/ipxe/efi/efi_entropy.h | 35 --- src/include/ipxe/entropy.h | 414 +++++++++++++++++++++------- src/include/ipxe/linux/linux_entropy.h | 34 --- src/include/ipxe/null_entropy.h | 52 ---- src/interface/efi/efi_entropy.c | 19 +- src/interface/linux/linux_entropy.c | 20 +- 19 files changed, 540 insertions(+), 619 deletions(-) delete mode 100644 src/arch/arm/include/bits/entropy.h delete mode 100644 src/arch/loong64/include/bits/entropy.h delete mode 100644 src/arch/x86/include/bits/entropy.h delete mode 100644 src/arch/x86/include/ipxe/rdrand.h delete mode 100644 src/arch/x86/include/ipxe/rtc_entropy.h create mode 100644 src/config/config_entropy.c delete mode 100644 src/crypto/null_entropy.c delete mode 100644 src/include/ipxe/efi/efi_entropy.h delete mode 100644 src/include/ipxe/linux/linux_entropy.h delete mode 100644 src/include/ipxe/null_entropy.h (limited to 'src/interface') diff --git a/src/arch/arm/include/bits/entropy.h b/src/arch/arm/include/bits/entropy.h deleted file mode 100644 index 75fdc90ea..000000000 --- a/src/arch/arm/include/bits/entropy.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _BITS_ENTROPY_H -#define _BITS_ENTROPY_H - -/** @file - * - * ARM-specific entropy API implementations - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#endif /* _BITS_ENTROPY_H */ diff --git a/src/arch/loong64/include/bits/entropy.h b/src/arch/loong64/include/bits/entropy.h deleted file mode 100644 index 8d3726930..000000000 --- a/src/arch/loong64/include/bits/entropy.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _BITS_ENTROPY_H -#define _BITS_ENTROPY_H - -/** @file - * - * LoongArch64-specific entropy API implementations - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#endif /* _BITS_ENTROPY_H */ diff --git a/src/arch/x86/core/rdrand.c b/src/arch/x86/core/rdrand.c index 29605ab2f..850ab1f11 100644 --- a/src/arch/x86/core/rdrand.c +++ b/src/arch/x86/core/rdrand.c @@ -32,12 +32,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include + +struct entropy_source rdrand_entropy __entropy_source ( ENTROPY_PREFERRED ); /** Number of times to retry RDRAND instruction */ #define RDRAND_RETRY_COUNT 16 /** Colour for debug messages */ -#define colour CPUID_FEATURES_INTEL_ECX_RDRAND +#define colour &rdrand_entropy /** * Enable entropy gathering @@ -54,16 +57,15 @@ static int rdrand_entropy_enable ( void ) { return -ENOTSUP; } - return 0; -} - -/** - * Disable entropy gathering - * - */ -static void rdrand_entropy_disable ( void ) { + /* Data returned by RDRAND is theoretically full entropy, up + * to a security strength of 128 bits, so assume that each + * sample contains exactly 8 bits of entropy. + */ + if ( DRBG_SECURITY_STRENGTH > 128 ) + return -ENOTSUP; + entropy_init ( &rdrand_entropy, MIN_ENTROPY ( 8.0 ) ); - /* Nothing to do */ + return 0; } /** @@ -93,7 +95,9 @@ static int rdrand_get_noise ( noise_sample_t *noise ) { return 0; } -PROVIDE_ENTROPY_INLINE ( rdrand, min_entropy_per_sample ); -PROVIDE_ENTROPY ( rdrand, entropy_enable, rdrand_entropy_enable ); -PROVIDE_ENTROPY ( rdrand, entropy_disable, rdrand_entropy_disable ); -PROVIDE_ENTROPY ( rdrand, get_noise, rdrand_get_noise ); +/** Hardware random number generator entropy source */ +struct entropy_source rdrand_entropy __entropy_source ( ENTROPY_PREFERRED ) = { + .name = "rdrand", + .enable = rdrand_entropy_enable, + .get_noise = rdrand_get_noise, +}; diff --git a/src/arch/x86/include/bits/entropy.h b/src/arch/x86/include/bits/entropy.h deleted file mode 100644 index 7accea33f..000000000 --- a/src/arch/x86/include/bits/entropy.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _BITS_ENTROPY_H -#define _BITS_ENTROPY_H - -/** @file - * - * x86-specific entropy API implementations - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include -#include - -#endif /* _BITS_ENTROPY_H */ diff --git a/src/arch/x86/include/ipxe/rdrand.h b/src/arch/x86/include/ipxe/rdrand.h deleted file mode 100644 index c9c170fb0..000000000 --- a/src/arch/x86/include/ipxe/rdrand.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _IPXE_RDRAND_H -#define _IPXE_RDRAND_H - -/** @file - * - * Hardware random number generator - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include -#include - -#ifdef ENTROPY_RDRAND -#define ENTROPY_PREFIX_rdrand -#else -#define ENTROPY_PREFIX_rdrand __rdrand_ -#endif - -/** - * min-entropy per sample - * - * @ret min_entropy min-entropy of each sample - */ -static inline __always_inline min_entropy_t -ENTROPY_INLINE ( rdrand, min_entropy_per_sample ) ( void ) { - - /* Data returned by RDRAND is theoretically full entropy, up - * to a security strength of 128 bits. - */ - if ( DRBG_SECURITY_STRENGTH > 128 ) - return 0; - return MIN_ENTROPY ( 8 * sizeof ( noise_sample_t ) ); -} - -#endif /* _IPXE_RDRAND_H */ diff --git a/src/arch/x86/include/ipxe/rtc_entropy.h b/src/arch/x86/include/ipxe/rtc_entropy.h deleted file mode 100644 index 581abcd3e..000000000 --- a/src/arch/x86/include/ipxe/rtc_entropy.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _IPXE_RTC_ENTROPY_H -#define _IPXE_RTC_ENTROPY_H - -/** @file - * - * RTC-based entropy source - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include - -#ifdef ENTROPY_RTC -#define ENTROPY_PREFIX_rtc -#else -#define ENTROPY_PREFIX_rtc __rtc_ -#endif - -/** - * min-entropy per sample - * - * @ret min_entropy min-entropy of each sample - */ -static inline __always_inline min_entropy_t -ENTROPY_INLINE ( rtc, min_entropy_per_sample ) ( void ) { - - /* The min-entropy has been measured on several platforms - * using the entropy_sample test code. Modelling the samples - * as independent, and using a confidence level of 99.99%, the - * measurements were as follows: - * - * qemu-kvm : 7.38 bits - * VMware : 7.46 bits - * Physical hardware : 2.67 bits - * - * We choose the lowest of these (2.67 bits) and apply a 50% - * safety margin to allow for some potential non-independence - * of samples. - */ - return MIN_ENTROPY ( 1.3 ); -} - -extern uint8_t rtc_sample ( void ); - -/** - * Get noise sample - * - * @ret noise Noise sample - * @ret rc Return status code - */ -static inline __always_inline int -ENTROPY_INLINE ( rtc, get_noise ) ( noise_sample_t *noise ) { - - /* Get sample */ - *noise = rtc_sample(); - - /* Always successful */ - return 0; -} - -#endif /* _IPXE_RTC_ENTROPY_H */ diff --git a/src/arch/x86/interface/pcbios/rtc_entropy.c b/src/arch/x86/interface/pcbios/rtc_entropy.c index c400d8a78..8f47ff6b8 100644 --- a/src/arch/x86/interface/pcbios/rtc_entropy.c +++ b/src/arch/x86/interface/pcbios/rtc_entropy.c @@ -39,6 +39,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +struct entropy_source rtc_entropy __entropy_source ( ENTROPY_NORMAL ); + /** Maximum time to wait for an RTC interrupt, in milliseconds */ #define RTC_MAX_WAIT_MS 100 @@ -203,6 +205,21 @@ static int rtc_entropy_enable ( void ) { if ( ( rc = rtc_entropy_check() ) != 0 ) goto err_check; + /* The min-entropy has been measured on several platforms + * using the entropy_sample test code. Modelling the samples + * as independent, and using a confidence level of 99.99%, the + * measurements were as follows: + * + * qemu-kvm : 7.38 bits + * VMware : 7.46 bits + * Physical hardware : 2.67 bits + * + * We choose the lowest of these (2.67 bits) and apply a 50% + * safety margin to allow for some potential non-independence + * of samples. + */ + entropy_init ( &rtc_entropy, MIN_ENTROPY ( 1.3 ) ); + return 0; err_check: @@ -226,11 +243,12 @@ static void rtc_entropy_disable ( void ) { } /** - * Measure a single RTC tick + * Get noise sample * - * @ret delta Length of RTC tick (in TSC units) + * @ret noise Noise sample + * @ret rc Return status code */ -uint8_t rtc_sample ( void ) { +static int rtc_get_noise ( noise_sample_t *noise ) { uint32_t before; uint32_t after; uint32_t temp; @@ -265,10 +283,14 @@ uint8_t rtc_sample ( void ) { : "=a" ( after ), "=d" ( before ), "=Q" ( temp ) : "2" ( 0 ) ); - return ( after - before ); + *noise = ( after - before ); + return 0; } -PROVIDE_ENTROPY_INLINE ( rtc, min_entropy_per_sample ); -PROVIDE_ENTROPY ( rtc, entropy_enable, rtc_entropy_enable ); -PROVIDE_ENTROPY ( rtc, entropy_disable, rtc_entropy_disable ); -PROVIDE_ENTROPY_INLINE ( rtc, get_noise ); +/** RTC entropy source */ +struct entropy_source rtc_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "rtc", + .enable = rtc_entropy_enable, + .disable = rtc_entropy_disable, + .get_noise = rtc_get_noise, +}; diff --git a/src/config/config_entropy.c b/src/config/config_entropy.c new file mode 100644 index 000000000..e96019a58 --- /dev/null +++ b/src/config/config_entropy.c @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** @file + * + * Entropy configuration options + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +/* + * Drag in entropy sources + */ +#ifdef ENTROPY_RTC +REQUIRE_OBJECT ( rtc_entropy ); +#endif +#ifdef ENTROPY_EFI +REQUIRE_OBJECT ( efi_entropy ); +#endif +#ifdef ENTROPY_LINUX +REQUIRE_OBJECT ( linux_entropy ); +#endif +#ifdef ENTROPY_RDRAND +REQUIRE_OBJECT ( rdrand ); +#endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 625ae055c..16c561660 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -50,6 +50,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #if defined ( __i386__ ) || defined ( __x86_64__ ) #define IOAPI_X86 #define NAP_EFIX86 +#define ENTROPY_RDRAND #define CPUID_CMD /* x86 CPU feature detection command */ #define UNSAFE_STD /* Avoid setting direction flag */ #endif diff --git a/src/config/defaults/linux.h b/src/config/defaults/linux.h index 5c4106d30..21de2a2e2 100644 --- a/src/config/defaults/linux.h +++ b/src/config/defaults/linux.h @@ -33,4 +33,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define SANBOOT_PROTO_FCP #define SANBOOT_PROTO_HTTP +#if defined ( __i386__ ) || defined ( __x86_64__ ) +#define ENTROPY_RDRAND +#endif + #endif /* CONFIG_DEFAULTS_LINUX_H */ diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h index 83835805a..ee342d41b 100644 --- a/src/config/defaults/pcbios.h +++ b/src/config/defaults/pcbios.h @@ -20,6 +20,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define SMBIOS_PCBIOS #define SANBOOT_PCBIOS #define ENTROPY_RTC +#define ENTROPY_RDRAND #define TIME_RTC #define REBOOT_PCBIOS #define ACPI_RSDP diff --git a/src/crypto/entropy.c b/src/crypto/entropy.c index ced6fd921..204e6bb1e 100644 --- a/src/crypto/entropy.c +++ b/src/crypto/entropy.c @@ -50,46 +50,61 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define EINFO_EPIPE_ADAPTIVE_PROPORTION_TEST \ __einfo_uniqify ( EINFO_EPIPE, 0x02, "Adaptive proportion test failed" ) +/** Current entropy source */ +static struct entropy_source *entropy_source; + /** - * Calculate cutoff value for the repetition count test - * - * @ret cutoff Cutoff value + * Enable entropy gathering * - * This is the cutoff value for the Repetition Count Test defined in - * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.2. + * @ret rc Return status code */ -static inline __attribute__ (( always_inline )) unsigned int -repetition_count_cutoff ( void ) { - double max_repetitions; - unsigned int cutoff; +int entropy_enable ( void ) { + int rc; - /* The cutoff formula for the repetition test is: - * - * C = ( 1 + ( -log2(W) / H_min ) ) - * - * where W is set at 2^(-30) (in ANS X9.82 Part 2 (October - * 2011 Draft) Section 8.5.2.1.3.1). - */ - max_repetitions = ( 1 + ( MIN_ENTROPY ( 30 ) / - min_entropy_per_sample() ) ); + /* Enable selected source, if applicable */ + if ( entropy_source ) { - /* Round up to a whole number of repetitions. We don't have - * the ceil() function available, so do the rounding by hand. - */ - cutoff = max_repetitions; - if ( cutoff < max_repetitions ) - cutoff++; - linker_assert ( ( cutoff >= max_repetitions ), rounding_error ); - - /* Floating-point operations are not allowed in iPXE since we - * never set up a suitable environment. Abort the build - * unless the calculated number of repetitions is a - * compile-time constant. - */ - linker_assert ( __builtin_constant_p ( cutoff ), - repetition_count_cutoff_not_constant ); + /* Enable entropy source */ + if ( ( rc = entropy_source->enable() ) != 0 ) { + DBGC ( &entropy_source, "ENTROPY could not enable " + "source \"%s\": %s\n", entropy_source->name, + strerror ( rc ) ); + return rc; + } - return cutoff; + /* Sanity checks */ + assert ( entropy_source->min_entropy_per_sample > 0 ); + assert ( entropy_source->repetition_count_cutoff > 0 ); + assert ( entropy_source->adaptive_proportion_cutoff > 0 ); + assert ( entropy_source->startup_test_count > 0 ); + + return 0; + } + + /* Find the first working source */ + rc = -ENOENT; + for_each_table_entry ( entropy_source, ENTROPY_SOURCES ) { + if ( ( rc = entropy_enable() ) == 0 ) { + DBGC ( &entropy_source, "ENTROPY using source \"%s\"\n", + entropy_source->name ); + break; + } + } + return rc; +} + +/** + * Disable entropy gathering + * + */ +void entropy_disable ( void ) { + + /* Sanity check */ + assert ( entropy_source != NULL ); + + /* Disable entropy gathering, if applicable */ + if ( entropy_source->disable ) + entropy_source->disable(); } /** @@ -104,6 +119,8 @@ repetition_count_cutoff ( void ) { static int repetition_count_test ( noise_sample_t sample ) { static noise_sample_t most_recent_sample; static unsigned int repetition_count = 0; + unsigned int repetition_count_cutoff = + entropy_source->repetition_count_cutoff; /* A = the most recently seen sample value * B = the number of times that value A has been seen in a row @@ -124,7 +141,7 @@ static int repetition_count_test ( noise_sample_t sample ) { /* i. If B >= C, then an error condition is raised * due to a failure of the test */ - if ( repetition_count >= repetition_count_cutoff() ) + if ( repetition_count >= repetition_count_cutoff ) return -EPIPE_REPETITION_COUNT_TEST; } else { @@ -141,117 +158,6 @@ static int repetition_count_test ( noise_sample_t sample ) { return 0; } -/** - * Window size for the adaptive proportion test - * - * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.3.1.1 allows - * five possible window sizes: 16, 64, 256, 4096 and 65536. - * - * We expect to generate relatively few (<256) entropy samples during - * a typical iPXE run; the use of a large window size would mean that - * the test would never complete a single cycle. We use a window size - * of 64, which is the smallest window size that permits values of - * H_min down to one bit per sample. - */ -#define ADAPTIVE_PROPORTION_WINDOW_SIZE 64 - -/** - * Combine adaptive proportion test window size and min-entropy - * - * @v n N (window size) - * @v h H (min-entropy) - * @ret n_h (N,H) combined value - */ -#define APC_N_H( n, h ) ( ( (n) << 8 ) | (h) ) - -/** - * Define a row of the adaptive proportion cutoff table - * - * @v h H (min-entropy) - * @v c16 Cutoff for N=16 - * @v c64 Cutoff for N=64 - * @v c256 Cutoff for N=256 - * @v c4096 Cutoff for N=4096 - * @v c65536 Cutoff for N=65536 - */ -#define APC_TABLE_ROW( h, c16, c64, c256, c4096, c65536) \ - case APC_N_H ( 16, h ) : return c16; \ - case APC_N_H ( 64, h ) : return c64; \ - case APC_N_H ( 256, h ) : return c256; \ - case APC_N_H ( 4096, h ) : return c4096; \ - case APC_N_H ( 65536, h ) : return c65536; - -/** Value used to represent "N/A" in adaptive proportion cutoff table */ -#define APC_NA 0 - -/** - * Look up value in adaptive proportion test cutoff table - * - * @v n N (window size) - * @v h H (min-entropy) - * @ret cutoff Cutoff - * - * This is the table of cutoff values defined in ANS X9.82 Part 2 - * (October 2011 Draft) Section 8.5.2.1.3.1.2. - */ -static inline __attribute__ (( always_inline )) unsigned int -adaptive_proportion_cutoff_lookup ( unsigned int n, unsigned int h ) { - switch ( APC_N_H ( n, h ) ) { - APC_TABLE_ROW ( 1, APC_NA, 51, 168, 2240, 33537 ); - APC_TABLE_ROW ( 2, APC_NA, 35, 100, 1193, 17053 ); - APC_TABLE_ROW ( 3, 10, 24, 61, 643, 8705 ); - APC_TABLE_ROW ( 4, 8, 16, 38, 354, 4473 ); - APC_TABLE_ROW ( 5, 6, 12, 25, 200, 2321 ); - APC_TABLE_ROW ( 6, 5, 9, 17, 117, 1220 ); - APC_TABLE_ROW ( 7, 4, 7, 15, 71, 653 ); - APC_TABLE_ROW ( 8, 4, 5, 9, 45, 358 ); - APC_TABLE_ROW ( 9, 3, 4, 7, 30, 202 ); - APC_TABLE_ROW ( 10, 3, 4, 5, 21, 118 ); - APC_TABLE_ROW ( 11, 2, 3, 4, 15, 71 ); - APC_TABLE_ROW ( 12, 2, 3, 4, 11, 45 ); - APC_TABLE_ROW ( 13, 2, 2, 3, 9, 30 ); - APC_TABLE_ROW ( 14, 2, 2, 3, 7, 21 ); - APC_TABLE_ROW ( 15, 1, 2, 2, 6, 15 ); - APC_TABLE_ROW ( 16, 1, 2, 2, 5, 11 ); - APC_TABLE_ROW ( 17, 1, 1, 2, 4, 9 ); - APC_TABLE_ROW ( 18, 1, 1, 2, 4, 7 ); - APC_TABLE_ROW ( 19, 1, 1, 1, 3, 6 ); - APC_TABLE_ROW ( 20, 1, 1, 1, 3, 5 ); - default: - return APC_NA; - } -} - -/** - * Calculate cutoff value for the adaptive proportion test - * - * @ret cutoff Cutoff value - * - * This is the cutoff value for the Adaptive Proportion Test defined - * in ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.3.1.2. - */ -static inline __attribute__ (( always_inline )) unsigned int -adaptive_proportion_cutoff ( void ) { - unsigned int h; - unsigned int n; - unsigned int cutoff; - - /* Look up cutoff value in cutoff table */ - n = ADAPTIVE_PROPORTION_WINDOW_SIZE; - h = ( min_entropy_per_sample() / MIN_ENTROPY_SCALE ); - cutoff = adaptive_proportion_cutoff_lookup ( n, h ); - - /* Fail unless cutoff value is a build-time constant */ - linker_assert ( __builtin_constant_p ( cutoff ), - adaptive_proportion_cutoff_not_constant ); - - /* Fail if cutoff value is N/A */ - linker_assert ( ( cutoff != APC_NA ), - adaptive_proportion_cutoff_not_applicable ); - - return cutoff; -} - /** * Perform adaptive proportion test * @@ -265,6 +171,8 @@ static int adaptive_proportion_test ( noise_sample_t sample ) { static noise_sample_t current_counted_sample; static unsigned int sample_count = ADAPTIVE_PROPORTION_WINDOW_SIZE; static unsigned int repetition_count; + unsigned int adaptive_proportion_cutoff = + entropy_source->adaptive_proportion_cutoff; /* A = the sample value currently being counted * B = the number of samples examined in this run of the test so far @@ -312,7 +220,7 @@ static int adaptive_proportion_test ( noise_sample_t sample ) { * condition, because the test has * detected a failure */ - if ( repetition_count > adaptive_proportion_cutoff() ) + if ( repetition_count > adaptive_proportion_cutoff ) return -EPIPE_ADAPTIVE_PROPORTION_TEST; } @@ -321,6 +229,23 @@ static int adaptive_proportion_test ( noise_sample_t sample ) { return 0; } +/** + * Get noise sample + * + * @ret noise Noise sample + * @ret rc Return status code + * + * This is the GetNoise function defined in ANS X9.82 Part 2 + * (October 2011 Draft) Section 6.5.2. + */ +int get_noise ( noise_sample_t *noise ) { + + /* Sanity check */ + assert ( entropy_source != NULL ); + + return entropy_source->get_noise ( noise ); +} + /** * Get entropy sample * @@ -334,6 +259,9 @@ static int get_entropy ( entropy_sample_t *entropy ) { static int rc = 0; noise_sample_t noise; + /* Sanity check */ + assert ( entropy_source != NULL ); + /* Any failure is permanent */ if ( rc != 0 ) return rc; @@ -357,31 +285,6 @@ static int get_entropy ( entropy_sample_t *entropy ) { return 0; } -/** - * Calculate number of samples required for startup tests - * - * @ret num_samples Number of samples required - * - * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.5 requires - * that at least one full cycle of the continuous tests must be - * performed at start-up. - */ -static inline __attribute__ (( always_inline )) unsigned int -startup_test_count ( void ) { - unsigned int num_samples; - - /* At least max(N,C) samples shall be generated by the noise - * source for start-up testing. - */ - num_samples = repetition_count_cutoff(); - if ( num_samples < adaptive_proportion_cutoff() ) - num_samples = adaptive_proportion_cutoff(); - linker_assert ( __builtin_constant_p ( num_samples ), - startup_test_count_not_constant ); - - return num_samples; -} - /** * Create next nonce value * @@ -402,7 +305,7 @@ static uint32_t make_next_nonce ( void ) { /** * Obtain entropy input temporary buffer * - * @v num_samples Number of entropy samples + * @v min_entropy Min-entropy required * @v tmp Temporary buffer * @v tmp_len Length of temporary buffer * @ret rc Return status code @@ -412,11 +315,8 @@ static uint32_t make_next_nonce ( void ) { * and condensing each entropy source output after each GetEntropy * call) as defined in ANS X9.82 Part 4 (April 2011 Draft) Section * 13.3.4.2. - * - * To minimise code size, the number of samples required is calculated - * at compilation time. */ -int get_entropy_input_tmp ( unsigned int num_samples, uint8_t *tmp, +int get_entropy_input_tmp ( min_entropy_t min_entropy, uint8_t *tmp, size_t tmp_len ) { static unsigned int startup_tested = 0; struct { @@ -424,6 +324,8 @@ int get_entropy_input_tmp ( unsigned int num_samples, uint8_t *tmp, entropy_sample_t sample; } __attribute__ (( packed )) data;; uint8_t df_buf[tmp_len]; + min_entropy_t entropy_total; + unsigned int num_samples; unsigned int i; int rc; @@ -432,22 +334,20 @@ int get_entropy_input_tmp ( unsigned int num_samples, uint8_t *tmp, return rc; /* Perform mandatory startup tests, if not yet performed */ - for ( ; startup_tested < startup_test_count() ; startup_tested++ ) { + for ( ; startup_tested < entropy_source->startup_test_count ; + startup_tested++ ) { if ( ( rc = get_entropy ( &data.sample ) ) != 0 ) goto err_get_entropy; } - /* 3. entropy_total = 0 - * - * (Nothing to do; the number of entropy samples required has - * already been precalculated.) - */ + /* 3. entropy_total = 0 */ + entropy_total = MIN_ENTROPY ( 0 ); /* 4. tmp = a fixed n-bit value, such as 0^n */ memset ( tmp, 0, tmp_len ); /* 5. While ( entropy_total < min_entropy ) */ - while ( num_samples-- ) { + for ( num_samples = 0 ; entropy_total < min_entropy ; num_samples++ ) { /* 5.1. ( status, entropy_bitstring, assessed_entropy ) * = GetEntropy() * 5.2. If status indicates an error, return ( status, Null ) @@ -466,19 +366,24 @@ int get_entropy_input_tmp ( unsigned int num_samples, uint8_t *tmp, for ( i = 0 ; i < tmp_len ; i++ ) tmp[i] ^= df_buf[i]; - /* 5.5. entropy_total = entropy_total + assessed_entropy - * - * (Nothing to do; the number of entropy samples - * required has already been precalculated.) - */ + /* 5.5. entropy_total = entropy_total + assessed_entropy */ + entropy_total += entropy_source->min_entropy_per_sample; } /* Disable entropy gathering */ entropy_disable(); + DBGC ( &entropy_source, "ENTROPY gathered %d bits in %d samples\n", + ( min_entropy / MIN_ENTROPY_SCALE ), num_samples ); return 0; err_get_entropy: entropy_disable(); return rc; } + +/* Drag in objects via entropy_enable */ +REQUIRING_SYMBOL ( entropy_enable ); + +/* Drag in entropy configuration */ +REQUIRE_OBJECT ( config_entropy ); diff --git a/src/crypto/null_entropy.c b/src/crypto/null_entropy.c deleted file mode 100644 index d1e1a6f73..000000000 --- a/src/crypto/null_entropy.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2012 Michael Brown . - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * - * You can also choose to distribute this program under the terms of - * the Unmodified Binary Distribution Licence (as given in the file - * COPYING.UBDL), provided that you have satisfied its requirements. - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -/** @file - * - * Nonexistent entropy source - * - * - * This source provides no entropy and must NOT be used in a - * security-sensitive environment. - */ - -#include - -PROVIDE_ENTROPY_INLINE ( null, min_entropy_per_sample ); -PROVIDE_ENTROPY_INLINE ( null, entropy_enable ); -PROVIDE_ENTROPY_INLINE ( null, entropy_disable ); -PROVIDE_ENTROPY_INLINE ( null, get_noise ); diff --git a/src/include/ipxe/efi/efi_entropy.h b/src/include/ipxe/efi/efi_entropy.h deleted file mode 100644 index 5b16fd7f9..000000000 --- a/src/include/ipxe/efi/efi_entropy.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _IPXE_EFI_ENTROPY_H -#define _IPXE_EFI_ENTROPY_H - -/** @file - * - * EFI entropy source - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include - -#ifdef ENTROPY_EFI -#define ENTROPY_PREFIX_efi -#else -#define ENTROPY_PREFIX_efi __efi_ -#endif - -/** - * min-entropy per sample - * - * @ret min_entropy min-entropy of each sample - */ -static inline __always_inline min_entropy_t -ENTROPY_INLINE ( efi, min_entropy_per_sample ) ( void ) { - - /* We use essentially the same mechanism as for the BIOS - * RTC-based entropy source, and so assume the same - * min-entropy per sample. - */ - return MIN_ENTROPY ( 1.3 ); -} - -#endif /* _IPXE_EFI_ENTROPY_H */ diff --git a/src/include/ipxe/entropy.h b/src/include/ipxe/entropy.h index d2e3ce501..108c37669 100644 --- a/src/include/ipxe/entropy.h +++ b/src/include/ipxe/entropy.h @@ -12,40 +12,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include -#include #include #include +#include #include -/** - * Calculate static inline entropy API function name - * - * @v _prefix Subsystem prefix - * @v _api_func API function - * @ret _subsys_func Subsystem API function - */ -#define ENTROPY_INLINE( _subsys, _api_func ) \ - SINGLE_API_INLINE ( ENTROPY_PREFIX_ ## _subsys, _api_func ) - -/** - * Provide a entropy API implementation - * - * @v _prefix Subsystem prefix - * @v _api_func API function - * @v _func Implementing function - */ -#define PROVIDE_ENTROPY( _subsys, _api_func, _func ) \ - PROVIDE_SINGLE_API ( ENTROPY_PREFIX_ ## _subsys, _api_func, _func ) - -/** - * Provide a static inline entropy API implementation - * - * @v _prefix Subsystem prefix - * @v _api_func API function - */ -#define PROVIDE_ENTROPY_INLINE( _subsys, _api_func ) \ - PROVIDE_SINGLE_API_INLINE ( ENTROPY_PREFIX_ ## _subsys, _api_func ) - /** A noise sample */ typedef uint8_t noise_sample_t; @@ -71,56 +42,93 @@ typedef unsigned int min_entropy_t; #define MIN_ENTROPY( bits ) \ ( ( min_entropy_t ) ( (bits) * MIN_ENTROPY_SCALE ) ) -/* Include all architecture-independent entropy API headers */ -#include -#include -#include +/** An entropy source */ +struct entropy_source { + /** Name */ + const char *name; + /** + * min-entropy per sample + * + * min-entropy is defined in ANS X9.82 Part 1-2006 Section 8.3 and in + * NIST SP 800-90 Appendix C.3 as + * + * H_min = -log2 ( p_max ) + * + * where p_max is the probability of the most likely sample value. + * + * Filled in by entropy_init(). + */ + min_entropy_t min_entropy_per_sample; + /** + * Repetition count test cutoff value + * + * This is the cutoff value for the Repetition Count Test + * defined in ANS X9.82 Part 2 (October 2011 Draft) Section + * 8.5.2.1.2. + * + * Filled in by entropy_init(). + */ + unsigned int repetition_count_cutoff; + /** + * Adaptive proportion test cutoff value + * + * This is the cutoff value for the Adaptive Proportion Test + * defined in ANS X9.82 Part 2 (October 2011 Draft) Section + * 8.5.2.1.3.1.2. + * + * Filled in by entropy_init(). + */ + unsigned int adaptive_proportion_cutoff; + /** + * Startup test count + * + * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.5 + * requires that at least one full cycle of the continuous + * tests must be performed at start-up. + */ + unsigned int startup_test_count; + /** + * Enable entropy gathering + * + * @ret rc Return status code + */ + int ( * enable ) ( void ); + /** + * Disable entropy gathering + * + */ + void ( * disable ) ( void ); + /** + * Get noise sample + * + * @ret noise Noise sample + * @ret rc Return status code + * + * This is the GetNoise function defined in ANS X9.82 Part 2 + * (October 2011 Draft) Section 6.5.2. + */ + int ( * get_noise ) ( noise_sample_t *noise ); +}; -/* Include all architecture-dependent entropy API headers */ -#include +/** Entropy source table */ +#define ENTROPY_SOURCES __table ( struct entropy_source, "entropy_sources" ) -/** - * Enable entropy gathering - * - * @ret rc Return status code - */ -int entropy_enable ( void ); +/** Declare an entropy source */ +#define __entropy_source( order ) __table_entry ( ENTROPY_SOURCES, order ) -/** - * Disable entropy gathering +/** @defgroup entropy_source_order Entropy source order * + * @{ */ -void entropy_disable ( void ); -/** - * min-entropy per sample - * - * @ret min_entropy min-entropy of each sample - * - * min-entropy is defined in ANS X9.82 Part 1-2006 Section 8.3 and in - * NIST SP 800-90 Appendix C.3 as - * - * H_min = -log2 ( p_max ) - * - * where p_max is the probability of the most likely sample value. - * - * This must be a compile-time constant. - */ -min_entropy_t min_entropy_per_sample ( void ); +#define ENTROPY_PREFERRED 01 /**< Preferred entropy source */ +#define ENTROPY_NORMAL 02 /**< Normal entropy source */ +#define ENTROPY_FALLBACK 03 /**< Fallback entropy source */ -/** - * Get noise sample - * - * @ret noise Noise sample - * @ret rc Return status code - * - * This is the GetNoise function defined in ANS X9.82 Part 2 - * (October 2011 Draft) Section 6.5.2. - */ -int get_noise ( noise_sample_t *noise ); +/** @} */ -extern int get_entropy_input_tmp ( unsigned int num_samples, - uint8_t *tmp, size_t tmp_len ); +extern int get_entropy_input_tmp ( min_entropy_t min_entropy, uint8_t *tmp, + size_t tmp_len ); /** Use SHA-256 as the underlying hash algorithm for Hash_df * @@ -145,8 +153,8 @@ extern int get_entropy_input_tmp ( unsigned int num_samples, * each entropy source output after each GetEntropy call) as defined * in ANS X9.82 Part 4 (April 2011 Draft) Section 13.3.4.2. * - * To minimise code size, the number of samples required is calculated - * at compilation time. + * This function is inlined since the entropy amount and length inputs + * are always compile-time constants. */ static inline __attribute__ (( always_inline )) int get_entropy_input ( unsigned int min_entropy_bits, void *data, size_t min_len, @@ -154,41 +162,16 @@ get_entropy_input ( unsigned int min_entropy_bits, void *data, size_t min_len, size_t tmp_len = ( ( ( min_entropy_bits * 2 ) + 7 ) / 8 ); uint8_t tmp_buf[ tmp_len ]; uint8_t *tmp = ( ( tmp_len > max_len ) ? tmp_buf : data ); - double min_samples; - unsigned int num_samples; unsigned int n; int rc; - /* Sanity checks */ - linker_assert ( ( min_entropy_per_sample() <= - MIN_ENTROPY ( 8 * sizeof ( noise_sample_t ) ) ), - min_entropy_per_sample_is_impossibly_high ); + /* Sanity check */ linker_assert ( ( min_entropy_bits <= ( 8 * max_len ) ), entropy_buffer_too_small ); /* Round up minimum entropy to an integral number of bytes */ min_entropy_bits = ( ( min_entropy_bits + 7 ) & ~7 ); - /* Calculate number of samples required to contain sufficient entropy */ - min_samples = ( MIN_ENTROPY ( min_entropy_bits ) / - min_entropy_per_sample() ); - - /* Round up to a whole number of samples. We don't have the - * ceil() function available, so do the rounding by hand. - */ - num_samples = min_samples; - if ( num_samples < min_samples ) - num_samples++; - linker_assert ( ( num_samples >= min_samples ), rounding_error ); - - /* Floating-point operations are not allowed in iPXE since we - * never set up a suitable environment. Abort the build - * unless the calculated number of samples is a compile-time - * constant. - */ - linker_assert ( __builtin_constant_p ( num_samples ), - num_samples_not_constant ); - /* (Unnumbered). The output length of the hash function shall * meet or exceed the security strength indicated by the * min_entropy parameter. @@ -218,8 +201,10 @@ get_entropy_input ( unsigned int min_entropy_bits, void *data, size_t min_len, linker_assert ( __builtin_constant_p ( tmp_len ), tmp_len_not_constant ); linker_assert ( ( n == ( 8 * tmp_len ) ), tmp_len_mismatch ); - if ( ( rc = get_entropy_input_tmp ( num_samples, tmp, tmp_len ) ) != 0 ) + if ( ( rc = get_entropy_input_tmp ( MIN_ENTROPY ( min_entropy_bits ), + tmp, tmp_len ) ) != 0 ) { return rc; + } /* 6. If ( n < min_length ), then tmp = tmp || 0^(min_length-n) * 7. If ( n > max_length ), then tmp = df ( tmp, max_length ) @@ -242,4 +227,231 @@ get_entropy_input ( unsigned int min_entropy_bits, void *data, size_t min_len, } } +/** + * Calculate cutoff value for the repetition count test + * + * @v min_entropy_per_sample Min-entropy per sample + * @ret cutoff Cutoff value + * + * This is the cutoff value for the Repetition Count Test defined in + * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.2. + */ +static inline __attribute__ (( always_inline )) unsigned int +entropy_repetition_count_cutoff ( min_entropy_t min_entropy_per_sample ) { + double max_repetitions; + unsigned int cutoff; + + /* The cutoff formula for the repetition test is: + * + * C = ( 1 + ( -log2(W) / H_min ) ) + * + * where W is set at 2^(-30) (in ANS X9.82 Part 2 (October + * 2011 Draft) Section 8.5.2.1.3.1). + */ + max_repetitions = ( 1 + ( MIN_ENTROPY ( 30 ) / + min_entropy_per_sample ) ); + + /* Round up to a whole number of repetitions. We don't have + * the ceil() function available, so do the rounding by hand. + */ + cutoff = max_repetitions; + if ( cutoff < max_repetitions ) + cutoff++; + linker_assert ( ( cutoff >= max_repetitions ), rounding_error ); + + /* Floating-point operations are not allowed in iPXE since we + * never set up a suitable environment. Abort the build + * unless the calculated number of repetitions is a + * compile-time constant. + */ + linker_assert ( __builtin_constant_p ( cutoff ), + repetition_count_cutoff_not_constant ); + + return cutoff; +} + +/** + * Window size for the adaptive proportion test + * + * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.3.1.1 allows + * five possible window sizes: 16, 64, 256, 4096 and 65536. + * + * We expect to generate relatively few (<256) entropy samples during + * a typical iPXE run; the use of a large window size would mean that + * the test would never complete a single cycle. We use a window size + * of 64, which is the smallest window size that permits values of + * H_min down to one bit per sample. + */ +#define ADAPTIVE_PROPORTION_WINDOW_SIZE 64 + +/** + * Combine adaptive proportion test window size and min-entropy + * + * @v n N (window size) + * @v h H (min-entropy) + * @ret n_h (N,H) combined value + */ +#define APC_N_H( n, h ) ( ( (n) << 8 ) | (h) ) + +/** + * Define a row of the adaptive proportion cutoff table + * + * @v h H (min-entropy) + * @v c16 Cutoff for N=16 + * @v c64 Cutoff for N=64 + * @v c256 Cutoff for N=256 + * @v c4096 Cutoff for N=4096 + * @v c65536 Cutoff for N=65536 + */ +#define APC_TABLE_ROW( h, c16, c64, c256, c4096, c65536) \ + case APC_N_H ( 16, h ) : return c16; \ + case APC_N_H ( 64, h ) : return c64; \ + case APC_N_H ( 256, h ) : return c256; \ + case APC_N_H ( 4096, h ) : return c4096; \ + case APC_N_H ( 65536, h ) : return c65536; + +/** Value used to represent "N/A" in adaptive proportion cutoff table */ +#define APC_NA 0 + +/** + * Look up value in adaptive proportion test cutoff table + * + * @v n N (window size) + * @v h H (min-entropy) + * @ret cutoff Cutoff + * + * This is the table of cutoff values defined in ANS X9.82 Part 2 + * (October 2011 Draft) Section 8.5.2.1.3.1.2. + */ +static inline __attribute__ (( always_inline )) unsigned int +entropy_adaptive_proportion_cutoff_lookup ( unsigned int n, unsigned int h ) { + switch ( APC_N_H ( n, h ) ) { + APC_TABLE_ROW ( 1, APC_NA, 51, 168, 2240, 33537 ); + APC_TABLE_ROW ( 2, APC_NA, 35, 100, 1193, 17053 ); + APC_TABLE_ROW ( 3, 10, 24, 61, 643, 8705 ); + APC_TABLE_ROW ( 4, 8, 16, 38, 354, 4473 ); + APC_TABLE_ROW ( 5, 6, 12, 25, 200, 2321 ); + APC_TABLE_ROW ( 6, 5, 9, 17, 117, 1220 ); + APC_TABLE_ROW ( 7, 4, 7, 15, 71, 653 ); + APC_TABLE_ROW ( 8, 4, 5, 9, 45, 358 ); + APC_TABLE_ROW ( 9, 3, 4, 7, 30, 202 ); + APC_TABLE_ROW ( 10, 3, 4, 5, 21, 118 ); + APC_TABLE_ROW ( 11, 2, 3, 4, 15, 71 ); + APC_TABLE_ROW ( 12, 2, 3, 4, 11, 45 ); + APC_TABLE_ROW ( 13, 2, 2, 3, 9, 30 ); + APC_TABLE_ROW ( 14, 2, 2, 3, 7, 21 ); + APC_TABLE_ROW ( 15, 1, 2, 2, 6, 15 ); + APC_TABLE_ROW ( 16, 1, 2, 2, 5, 11 ); + APC_TABLE_ROW ( 17, 1, 1, 2, 4, 9 ); + APC_TABLE_ROW ( 18, 1, 1, 2, 4, 7 ); + APC_TABLE_ROW ( 19, 1, 1, 1, 3, 6 ); + APC_TABLE_ROW ( 20, 1, 1, 1, 3, 5 ); + default: + return APC_NA; + } +} + +/** + * Calculate cutoff value for the adaptive proportion test + * + * @v min_entropy_per_sample Min-entropy per sample + * @ret cutoff Cutoff value + * + * This is the cutoff value for the Adaptive Proportion Test defined + * in ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.3.1.2. + */ +static inline __attribute__ (( always_inline )) unsigned int +entropy_adaptive_proportion_cutoff ( min_entropy_t min_entropy_per_sample ) { + unsigned int h; + unsigned int n; + unsigned int cutoff; + + /* Look up cutoff value in cutoff table */ + n = ADAPTIVE_PROPORTION_WINDOW_SIZE; + h = ( min_entropy_per_sample / MIN_ENTROPY_SCALE ); + cutoff = entropy_adaptive_proportion_cutoff_lookup ( n, h ); + + /* Fail unless cutoff value is a compile-time constant */ + linker_assert ( __builtin_constant_p ( cutoff ), + adaptive_proportion_cutoff_not_constant ); + + /* Fail if cutoff value is N/A */ + linker_assert ( ( cutoff != APC_NA ), + adaptive_proportion_cutoff_not_applicable ); + + return cutoff; +} + +/** + * Calculate number of samples required for startup tests + * + * @v repetition_count_cutoff Repetition count test cutoff value + * @v adaptive_proportion_cutoff Adaptive proportion test cutoff value + * @ret num_samples Number of samples required + * + * ANS X9.82 Part 2 (October 2011 Draft) Section 8.5.2.1.5 requires + * that at least one full cycle of the continuous tests must be + * performed at start-up. + */ +static inline __attribute__ (( always_inline )) unsigned int +entropy_startup_test_count ( unsigned int repetition_count_cutoff, + unsigned int adaptive_proportion_cutoff ) { + unsigned int num_samples; + + /* At least max(N,C) samples shall be generated by the noise + * source for start-up testing. + */ + num_samples = repetition_count_cutoff; + if ( num_samples < adaptive_proportion_cutoff ) + num_samples = adaptive_proportion_cutoff; + linker_assert ( __builtin_constant_p ( num_samples ), + startup_test_count_not_constant ); + + return num_samples; +} + +/** + * Initialise entropy source + * + * @v source Entropy source + * @v min_entropy_per_sample Min-entropy per sample + * + * The cutoff value calculations for the repetition count test and the + * adaptive proportion test are provided as static inline functions + * since the results will always be compile-time constants. + */ +static inline __attribute__ (( always_inline )) void +entropy_init ( struct entropy_source *source, + min_entropy_t min_entropy_per_sample ) { + unsigned int repetition_count_cutoff; + unsigned int adaptive_proportion_cutoff; + unsigned int startup_test_count; + + /* Sanity check */ + linker_assert ( min_entropy_per_sample > MIN_ENTROPY ( 0 ), + min_entropy_per_sample_is_zero ); + linker_assert ( ( min_entropy_per_sample <= + MIN_ENTROPY ( 8 * sizeof ( noise_sample_t ) ) ), + min_entropy_per_sample_is_impossibly_high ); + + /* Calculate test cutoff values */ + repetition_count_cutoff = + entropy_repetition_count_cutoff ( min_entropy_per_sample ); + adaptive_proportion_cutoff = + entropy_adaptive_proportion_cutoff ( min_entropy_per_sample ); + startup_test_count = + entropy_startup_test_count ( repetition_count_cutoff, + adaptive_proportion_cutoff ); + + /* Record min-entropy per sample and test cutoff values */ + source->min_entropy_per_sample = min_entropy_per_sample; + source->repetition_count_cutoff = repetition_count_cutoff; + source->adaptive_proportion_cutoff = adaptive_proportion_cutoff; + source->startup_test_count = startup_test_count; +} + +extern int entropy_enable ( void ); +extern void entropy_disable ( void ); +extern int get_noise ( noise_sample_t *noise ); + #endif /* _IPXE_ENTROPY_H */ diff --git a/src/include/ipxe/linux/linux_entropy.h b/src/include/ipxe/linux/linux_entropy.h deleted file mode 100644 index ea8c1f16c..000000000 --- a/src/include/ipxe/linux/linux_entropy.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef _IPXE_LINUX_ENTROPY_H -#define _IPXE_LINUX_ENTROPY_H - -/** @file - * - * /dev/random-based entropy source - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#ifdef ENTROPY_LINUX -#define ENTROPY_PREFIX_linux -#else -#define ENTROPY_PREFIX_linux __linux_ -#endif - -/** - * min-entropy per sample - * - * @ret min_entropy min-entropy of each sample - */ -static inline __always_inline min_entropy_t -ENTROPY_INLINE ( linux, min_entropy_per_sample ) ( void ) { - - /* linux_get_noise() reads a single byte from /dev/random, - * which is supposed to block until a sufficient amount of - * entropy is available. We therefore assume that each sample - * contains exactly 8 bits of entropy. - */ - return MIN_ENTROPY ( 8.0 ); -} - -#endif /* _IPXE_LINUX_ENTROPY_H */ diff --git a/src/include/ipxe/null_entropy.h b/src/include/ipxe/null_entropy.h deleted file mode 100644 index 5a6bb6218..000000000 --- a/src/include/ipxe/null_entropy.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef _IPXE_NULL_ENTROPY_H -#define _IPXE_NULL_ENTROPY_H - -/** @file - * - * Nonexistent entropy source - * - * This source provides no entropy and must NOT be used in a - * security-sensitive environment. - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include - -#ifdef ENTROPY_NULL -#define ENTROPY_PREFIX_null -#else -#define ENTROPY_PREFIX_null __null_ -#endif - -static inline __always_inline int -ENTROPY_INLINE ( null, entropy_enable ) ( void ) { - /* Do nothing */ - return 0; -} - -static inline __always_inline void -ENTROPY_INLINE ( null, entropy_disable ) ( void ) { - /* Do nothing */ -} - -static inline __always_inline min_entropy_t -ENTROPY_INLINE ( null, min_entropy_per_sample ) ( void ) { - /* Actual amount of min-entropy is zero. To avoid - * division-by-zero errors and to allow compilation of - * entropy-consuming code, pretend to have 1 bit of entropy in - * each sample. - */ - return MIN_ENTROPY ( 1.0 ); -} - -static inline __always_inline int -ENTROPY_INLINE ( null, get_noise ) ( noise_sample_t *noise ) { - - /* All sample values are constant */ - *noise = 0x01; - - return 0; -} - -#endif /* _IPXE_NULL_ENTROPY_H */ diff --git a/src/interface/efi/efi_entropy.c b/src/interface/efi/efi_entropy.c index 1e8ddfb68..e5c393562 100644 --- a/src/interface/efi/efi_entropy.c +++ b/src/interface/efi/efi_entropy.c @@ -36,6 +36,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ); + /** Random number generator protocol */ static EFI_RNG_PROTOCOL *efirng; EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); @@ -91,6 +93,12 @@ static int efi_entropy_enable ( void ) { return rc; } + /* We use essentially the same mechanism as for the BIOS + * RTC-based entropy source, and so assume the same + * min-entropy per sample. + */ + entropy_init ( &efi_entropy, MIN_ENTROPY ( 1.3 ) ); + return 0; } @@ -235,7 +243,10 @@ static int efi_get_noise ( noise_sample_t *noise ) { return 0; } -PROVIDE_ENTROPY_INLINE ( efi, min_entropy_per_sample ); -PROVIDE_ENTROPY ( efi, entropy_enable, efi_entropy_enable ); -PROVIDE_ENTROPY ( efi, entropy_disable, efi_entropy_disable ); -PROVIDE_ENTROPY ( efi, get_noise, efi_get_noise ); +/** EFI entropy source */ +struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "efi", + .enable = efi_entropy_enable, + .disable = efi_entropy_disable, + .get_noise = efi_get_noise, +}; diff --git a/src/interface/linux/linux_entropy.c b/src/interface/linux/linux_entropy.c index 257e993a0..f24969794 100644 --- a/src/interface/linux/linux_entropy.c +++ b/src/interface/linux/linux_entropy.c @@ -34,6 +34,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +struct entropy_source linux_entropy __entropy_source ( ENTROPY_NORMAL ); + /** Entropy source filename */ static const char entropy_filename[] = "/dev/random"; @@ -55,6 +57,13 @@ static int linux_entropy_enable ( void ) { return entropy_fd; } + /* linux_get_noise() reads a single byte from /dev/random, + * which is supposed to block until a sufficient amount of + * entropy is available. We therefore assume that each sample + * contains exactly 8 bits of entropy. + */ + entropy_init ( &linux_entropy, MIN_ENTROPY ( 8.0 ) ); + return 0; } @@ -95,7 +104,10 @@ static int linux_get_noise ( noise_sample_t *noise ) { return 0; } -PROVIDE_ENTROPY_INLINE ( linux, min_entropy_per_sample ); -PROVIDE_ENTROPY ( linux, entropy_enable, linux_entropy_enable ); -PROVIDE_ENTROPY ( linux, entropy_disable, linux_entropy_disable ); -PROVIDE_ENTROPY ( linux, get_noise, linux_get_noise ); +/** Linux entropy source */ +struct entropy_source linux_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "linux", + .enable = linux_entropy_enable, + .disable = linux_entropy_disable, + .get_noise = linux_get_noise, +}; -- cgit v1.2.3-55-g7522 From 471599dc7721d454b6658062c901b52038a78be2 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 20 Feb 2023 14:08:49 +0000 Subject: [efi] Split out EFI_RNG_PROTOCOL as a separate entropy source Commit 7ca801d ("[efi] Use the EFI_RNG_PROTOCOL as an entropy source if available") added EFI_RNG_PROTOCOL as an alternative entropy source via an ad-hoc mechanism specific to efi_entropy.c. Split out EFI_RNG_PROTOCOL to a separate entropy source, and allow the entropy core to handle the selection of RDRAND, EFI_RNG_PROTOCOL, or timer ticks as the active source. The fault detection logic added in commit a87537d ("[efi] Detect and disable seriously broken EFI_RNG_PROTOCOL implementations") may be removed completely, since the failure will already be detected by the generic ANS X9.82-mandated repetition count test and will now be handled gracefully by the entropy core. Signed-off-by: Michael Brown --- src/config/config_entropy.c | 5 +- src/config/defaults/efi.h | 3 +- src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_entropy.c | 95 ++------------------------------ src/interface/efi/efi_rng.c | 118 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 src/interface/efi/efi_rng.c (limited to 'src/interface') diff --git a/src/config/config_entropy.c b/src/config/config_entropy.c index e96019a58..9f12f1fa3 100644 --- a/src/config/config_entropy.c +++ b/src/config/config_entropy.c @@ -37,9 +37,12 @@ PROVIDE_REQUIRING_SYMBOL(); #ifdef ENTROPY_RTC REQUIRE_OBJECT ( rtc_entropy ); #endif -#ifdef ENTROPY_EFI +#ifdef ENTROPY_EFITICK REQUIRE_OBJECT ( efi_entropy ); #endif +#ifdef ENTROPY_EFIRNG +REQUIRE_OBJECT ( efi_rng ); +#endif #ifdef ENTROPY_LINUX REQUIRE_OBJECT ( linux_entropy ); #endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 16c561660..8e53b9ab6 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -19,7 +19,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define SMBIOS_EFI #define SANBOOT_EFI #define BOFM_EFI -#define ENTROPY_EFI +#define ENTROPY_EFITICK +#define ENTROPY_EFIRNG #define TIME_EFI #define REBOOT_EFI #define ACPI_EFI diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index d7b6ea1bd..e6fd8524e 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -403,6 +403,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_pci_cmd ( ERRFILE_OTHER | 0x00590000 ) #define ERRFILE_dhe ( ERRFILE_OTHER | 0x005a0000 ) #define ERRFILE_efi_cmdline ( ERRFILE_OTHER | 0x005b0000 ) +#define ERRFILE_efi_rng ( ERRFILE_OTHER | 0x005c0000 ) /** @} */ diff --git a/src/interface/efi/efi_entropy.c b/src/interface/efi/efi_entropy.c index e5c393562..cda1c3640 100644 --- a/src/interface/efi/efi_entropy.c +++ b/src/interface/efi/efi_entropy.c @@ -25,10 +25,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -#include #include #include -#include /** @file * @@ -36,24 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ); - -/** Random number generator protocol */ -static EFI_RNG_PROTOCOL *efirng; -EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); - -/** Minimum number of bytes to request from RNG - * - * The UEFI spec states (for no apparently good reason) that "When a - * Deterministic Random Bit Generator (DRBG) is used on the output of - * a (raw) entropy source, its security level must be at least 256 - * bits." The EDK2 codebase (mis)interprets this to mean that the - * call to GetRNG() should fail if given a buffer less than 32 bytes. - * - * Incidentally, nothing in the EFI RNG protocol provides any way to - * report the actual amount of entropy returned by GetRNG(). - */ -#define EFI_ENTROPY_RNG_LEN 32 +struct entropy_source efitick_entropy __entropy_source ( ENTROPY_FALLBACK ); /** Time (in 100ns units) to delay waiting for timer tick * @@ -78,9 +59,6 @@ static int efi_entropy_enable ( void ) { EFI_STATUS efirc; int rc; - DBGC ( &tick, "ENTROPY %s RNG protocol\n", - ( efirng ? "has" : "has no" ) ); - /* Drop to external TPL to allow timer tick event to take place */ bs->RestoreTPL ( efi_external_tpl ); @@ -97,7 +75,7 @@ static int efi_entropy_enable ( void ) { * RTC-based entropy source, and so assume the same * min-entropy per sample. */ - entropy_init ( &efi_entropy, MIN_ENTROPY ( 1.3 ) ); + entropy_init ( &efitick_entropy, MIN_ENTROPY ( 1.3 ) ); return 0; } @@ -155,7 +133,7 @@ static int efi_entropy_tick ( void ) { * @ret noise Noise sample * @ret rc Return status code */ -static int efi_get_noise_ticks ( noise_sample_t *noise ) { +static int efi_get_noise ( noise_sample_t *noise ) { int before; int after; int rc; @@ -180,72 +158,9 @@ static int efi_get_noise_ticks ( noise_sample_t *noise ) { return 0; } -/** - * Get noise sample from RNG protocol - * - * @ret noise Noise sample - * @ret rc Return status code - */ -static int efi_get_noise_rng ( noise_sample_t *noise ) { - static uint8_t prev[EFI_ENTROPY_RNG_LEN]; - uint8_t buf[EFI_ENTROPY_RNG_LEN]; - EFI_STATUS efirc; - int rc; - - /* Fail if we have no EFI RNG protocol */ - if ( ! efirng ) - return -ENOTSUP; - - /* Get the minimum allowed number of random bytes */ - if ( ( efirc = efirng->GetRNG ( efirng, NULL, EFI_ENTROPY_RNG_LEN, - buf ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( &tick, "ENTROPY could not read from RNG: %s\n", - strerror ( rc ) ); - return rc; - } - - /* Fail (and permanently disable the EFI RNG) if we get - * consecutive identical results. - */ - if ( memcmp ( buf, prev, sizeof ( buf ) ) == 0 ) { - DBGC ( &tick, "ENTROPY detected broken EFI RNG:\n" ); - DBGC_HDA ( &tick, 0, buf, sizeof ( buf ) ); - efirng = NULL; - return -EIO; - } - memcpy ( prev, buf, sizeof ( prev ) ); - - /* Reduce random bytes to a single noise sample. This seems - * like overkill, but we have no way of knowing how much - * entropy is actually present in the bytes returned by the - * RNG protocol. - */ - *noise = crc32_le ( 0, buf, sizeof ( buf ) ); - - return 0; -} - -/** - * Get noise sample - * - * @ret noise Noise sample - * @ret rc Return status code - */ -static int efi_get_noise ( noise_sample_t *noise ) { - int rc; - - /* Try RNG first, falling back to timer ticks */ - if ( ( ( rc = efi_get_noise_rng ( noise ) ) != 0 ) && - ( ( rc = efi_get_noise_ticks ( noise ) ) != 0 ) ) - return rc; - - return 0; -} - /** EFI entropy source */ -struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ) = { - .name = "efi", +struct entropy_source efitick_entropy __entropy_source ( ENTROPY_FALLBACK ) = { + .name = "efitick", .enable = efi_entropy_enable, .disable = efi_entropy_disable, .get_noise = efi_get_noise, diff --git a/src/interface/efi/efi_rng.c b/src/interface/efi/efi_rng.c new file mode 100644 index 000000000..b76a6fc0d --- /dev/null +++ b/src/interface/efi/efi_rng.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include + +/** @file + * + * EFI random number generator protocol entropy source + * + */ + +struct entropy_source efirng_entropy __entropy_source ( ENTROPY_NORMAL ); + +/** Random number generator protocol */ +static EFI_RNG_PROTOCOL *efirng; +EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); + +/** Minimum number of bytes to request from RNG + * + * The UEFI spec states (for no apparently good reason) that "When a + * Deterministic Random Bit Generator (DRBG) is used on the output of + * a (raw) entropy source, its security level must be at least 256 + * bits." The EDK2 codebase (mis)interprets this to mean that the + * call to GetRNG() should fail if given a buffer less than 32 bytes. + * + * Incidentally, nothing in the EFI RNG protocol provides any way to + * report the actual amount of entropy returned by GetRNG(). + */ +#define EFIRNG_LEN 32 + +/** + * Enable entropy gathering + * + * @ret rc Return status code + */ +static int efirng_enable ( void ) { + + /* Check for RNG protocol support */ + if ( ! efirng ) { + DBGC ( &efirng, "EFIRNG has no RNG protocol\n" ); + return -ENOTSUP; + } + + /* Nothing in the EFI specification provides any clue as to + * how much entropy will be returned by GetRNG(). Make a + * totally uninformed (and conservative guess) that each + * sample will contain at least one bit of entropy. + */ + entropy_init ( &efirng_entropy, MIN_ENTROPY ( 1.0 ) ); + + return 0; +} + +/** + * Get noise sample from RNG protocol + * + * @ret noise Noise sample + * @ret rc Return status code + */ +static int efirng_get_noise ( noise_sample_t *noise ) { + uint8_t buf[EFIRNG_LEN]; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + assert ( efirng != NULL ); + + /* Get the minimum allowed number of random bytes */ + if ( ( efirc = efirng->GetRNG ( efirng, NULL, sizeof ( buf ), + buf ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efirng, "ENTROPY could not read from RNG: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Reduce random bytes to a single noise sample. This seems + * like overkill, but we have no way of knowing how much + * entropy is actually present in the bytes returned by the + * RNG protocol. + */ + *noise = crc32_le ( 0, buf, sizeof ( buf ) ); + + return 0; +} + +/** EFI random number generator protocol entropy source */ +struct entropy_source efirng_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "efirng", + .enable = efirng_enable, + .get_noise = efirng_get_noise, +}; -- cgit v1.2.3-55-g7522 From 04e60a278abcda47301f6be2c23755e5e1004661 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 28 Feb 2023 12:04:58 +0000 Subject: [efi] Omit EFI_LOAD_FILE2_PROTOCOL for a zero-length initrd When the Linux kernel is being used with no initrd, iPXE will still provide a zero-length initrd.magic file within the virtual filesystem. As of commit 6a004be ("[efi] Support the initrd autodetection mechanism in newer Linux kernels"), this zero-length file will also be exposed via an EFI_LOAD_FILE2_PROTOCOL instance on a handle with a fixed device path. The correct handling of zero-length files via EFI_LOAD_FILE2_PROTOCOL is unfortunately not well defined. Linux expects the first call to LoadFile() to always fail with EFI_BUFFER_TOO_SMALL. When the initrd is genuinely zero-length, iPXE will return success since the buffer is not too small to hold the (zero-length) file. This causes Linux to immediately report a spurious EFI_LOAD_ERROR boot failure. We could change the logic in iPXE's efi_file_load() to always return EFI_BUFFER_TOO_SMALL if Buffer is NULL on entry. Since the correct behaviour of LoadFile() in the corner case of a zero-length file is left undefined by the UEFI specification, this would be permissible. Unfortunately this approach would not fix the problem. If we return EFI_BUFFER_TOO_SMALL and set the file length to zero, then Linux will call the boot services AllocatePages() method with a zero length. In at least the EDK2 implementation, this combination of parameters will cause AllocatePages() to return EFI_OUT_OF_RESOURCES, and Linux will again report a boot failure. Another approach would be to install the initrd device path handle only if we have a non-empty initrd to offer. Unfortunately this would lead to a failure in yet another corner case: if a previous bootloader has installed an initrd device path handle (e.g. to pass a boot script to iPXE) then we must not leave that initrd in place, since then our loaded kernel would end up seeing the wrong initrd content. The cleanest fix seems to be to ensure that the initrd device path handle is installed with the EFI_DEVICE_PATH_PROTOCOL instance present but with the EFI_LOAD_FILE2_PROTOCOL instance absent (and forcibly uninstalled if necessary), matching the state in which we leave the handle after uninstalling our virtual filesystem. Linux will then not find any handle that supports EFI_LOAD_FILE2_PROTOCOL within the fixed device path, and so will fall through to trying other mechanisms to locate the initrd. Reported-by: Chris Bradshaw Signed-off-by: Michael Brown --- src/interface/efi/efi_file.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index e8debbbe1..7bcf8d597 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -1020,6 +1020,7 @@ static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path, */ int efi_file_install ( EFI_HANDLE handle ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_LOAD_FILE2_PROTOCOL *load; union { EFI_DISK_IO_PROTOCOL *diskio; void *interface; @@ -1082,9 +1083,17 @@ int efi_file_install ( EFI_HANDLE handle ) { } assert ( diskio.diskio == &efi_disk_io_protocol ); - /* Install Linux initrd fixed device path file */ + /* Install Linux initrd fixed device path file + * + * Install the device path handle unconditionally, since we + * are definitively the bootloader providing the initrd, if + * any, to the booted image. Install the load file protocol + * instance only if the initrd is non-empty, since Linux does + * not gracefully handle a zero-length initrd. + */ + load = ( list_is_singular ( &images ) ? NULL : &efi_file_initrd.load ); if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header, - &efi_file_initrd.load ) ) != 0 ) { + load ) ) != 0 ) { goto err_initrd; } -- cgit v1.2.3-55-g7522 From e51e7bbad7d043a6b369b0050ff4e1c0e62f3b5d Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 6 Mar 2023 16:55:54 +0000 Subject: [image] Consistently use for_each_image() to iterate over images Signed-off-by: Michael Brown --- src/core/image.c | 2 +- src/interface/efi/efi_file.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/interface') diff --git a/src/core/image.c b/src/core/image.c index f6d3d8ddd..5a9aebc74 100644 --- a/src/core/image.c +++ b/src/core/image.c @@ -312,7 +312,7 @@ void unregister_image ( struct image *image ) { struct image * find_image ( const char *name ) { struct image *image; - list_for_each_entry ( image, &images, list ) { + for_each_image ( image ) { if ( strcmp ( image->name, name ) == 0 ) return image; } diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index 7bcf8d597..f2b44fa73 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -130,7 +130,7 @@ static struct image * efi_file_find ( const char *name ) { struct image *image; /* Find image */ - list_for_each_entry ( image, &images, list ) { + for_each_image ( image ) { if ( strcasecmp ( image->name, name ) == 0 ) return image; } -- cgit v1.2.3-55-g7522 From 9e1f7a3659071004f4b8c76f2593da6287f0d575 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 6 Mar 2023 16:28:48 +0000 Subject: [image] Always unregister currently executing image We unregister script images during their execution, to prevent a "boot" command from re-executing the containing script. This also has the side effect of preventing executing scripts from showing up within the Linux magic initrd image (or the Multiboot module list). Additional logic in bzimage.c and efi_file.c prevents a currently executing kernel from showing up within the magic initrd image. Similar logic in multiboot.c prevents the Multiboot kernel from showing up as a Multiboot module. This still leaves some corner cases that are not covered correctly. For example: when using a gzip-compressed kernel image, nothing will currently hide the original compressed image from the magic initrd. Fix by moving the logic that temporarily unregisters the current image from script_exec() to image_exec(), so that it applies to all image types, and simplify the magic initrd and Multiboot module list construction logic on the basis that no further filtering of the registered image list is necessary. This change has the side effect of hiding currently executing EFI images from the virtual filesystem exposed by iPXE. For example, when using iPXE to boot wimboot, the wimboot binary itself will no longer be visible within the virtual filesystem. Signed-off-by: Michael Brown --- src/arch/x86/image/bzimage.c | 8 -------- src/arch/x86/image/multiboot.c | 4 ---- src/core/image.c | 12 +++++++++--- src/image/script.c | 9 --------- src/interface/efi/efi_file.c | 6 +----- 5 files changed, 10 insertions(+), 29 deletions(-) (limited to 'src/interface') diff --git a/src/arch/x86/image/bzimage.c b/src/arch/x86/image/bzimage.c index b40bb2d07..b15bd5563 100644 --- a/src/arch/x86/image/bzimage.c +++ b/src/arch/x86/image/bzimage.c @@ -355,10 +355,6 @@ static size_t bzimage_load_initrd ( struct image *image, size_t offset; size_t pad_len; - /* Do not include kernel image itself as an initrd */ - if ( initrd == image ) - return 0; - /* Create cpio header for non-prebuilt images */ offset = cpio_header ( initrd, &cpio ); @@ -406,10 +402,6 @@ static int bzimage_check_initrds ( struct image *image, /* Calculate total loaded length of initrds */ for_each_image ( initrd ) { - /* Skip kernel */ - if ( initrd == image ) - continue; - /* Calculate length */ len += bzimage_load_initrd ( image, initrd, UNULL ); len = bzimage_align ( len ); diff --git a/src/arch/x86/image/multiboot.c b/src/arch/x86/image/multiboot.c index 0c85df708..c1c63bc97 100644 --- a/src/arch/x86/image/multiboot.c +++ b/src/arch/x86/image/multiboot.c @@ -204,10 +204,6 @@ static int multiboot_add_modules ( struct image *image, physaddr_t start, break; } - /* Do not include kernel image itself as a module */ - if ( module_image == image ) - continue; - /* Page-align the module */ start = ( ( start + 0xfff ) & ~0xfff ); diff --git a/src/core/image.c b/src/core/image.c index 5a9aebc74..b280eb4d3 100644 --- a/src/core/image.c +++ b/src/core/image.c @@ -349,9 +349,8 @@ int image_exec ( struct image *image ) { /* Preserve record of any currently-running image */ saved_current_image = current_image; - /* Take out a temporary reference to the image. This allows - * the image to unregister itself if necessary, without - * automatically freeing itself. + /* Take out a temporary reference to the image, so that it + * does not get freed when temporarily unregistered. */ current_image = image_get ( image ); @@ -371,6 +370,9 @@ int image_exec ( struct image *image ) { /* Record boot attempt */ syslog ( LOG_NOTICE, "Executing \"%s\"\n", image->name ); + /* Temporarily unregister the image during its execution */ + unregister_image ( image ); + /* Try executing the image */ if ( ( rc = image->type->exec ( image ) ) != 0 ) { DBGC ( image, "IMAGE %s could not execute: %s\n", @@ -387,6 +389,10 @@ int image_exec ( struct image *image ) { image->name, strerror ( rc ) ); } + /* Re-register image (unless due to be replaced) */ + if ( ! image->replacement ) + register_image ( image ); + /* Pick up replacement image before we drop the original * image's temporary reference. The replacement image must * already be registered, so we don't need to hold a temporary diff --git a/src/image/script.c b/src/image/script.c index 28050868a..b34df1e21 100644 --- a/src/image/script.c +++ b/src/image/script.c @@ -197,11 +197,6 @@ static int script_exec ( struct image *image ) { size_t saved_offset; int rc; - /* Temporarily de-register image, so that a "boot" command - * doesn't throw us into an execution loop. - */ - unregister_image ( image ); - /* Preserve state of any currently-running script */ saved_offset = script_offset; @@ -212,10 +207,6 @@ static int script_exec ( struct image *image ) { /* Restore saved state */ script_offset = saved_offset; - /* Re-register image (unless we have been replaced) */ - if ( ! image->replacement ) - register_image ( image ); - return rc; } diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index f2b44fa73..a0bba1606 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -240,10 +240,6 @@ static size_t efi_file_read_initrd ( struct efi_file_reader *reader ) { len = 0; for_each_image ( image ) { - /* Ignore currently executing image */ - if ( image == current_image ) - continue; - /* Pad to alignment boundary */ pad_len = ( ( -reader->pos ) & ( INITRD_ALIGN - 1 ) ); if ( pad_len ) { @@ -1091,7 +1087,7 @@ int efi_file_install ( EFI_HANDLE handle ) { * instance only if the initrd is non-empty, since Linux does * not gracefully handle a zero-length initrd. */ - load = ( list_is_singular ( &images ) ? NULL : &efi_file_initrd.load ); + load = ( have_images() ? &efi_file_initrd.load : NULL ); if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header, load ) ) != 0 ) { goto err_initrd; -- cgit v1.2.3-55-g7522 From 09e8a154084c57311463408e3f2e412c305a9638 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 15 Mar 2023 16:20:16 +0000 Subject: [efi] Claim fixed device paths by uninstalling device path protocol As documented in commits 6a004be ("[efi] Support the initrd autodetection mechanism in newer Linux kernels") and 04e60a2 ("[efi] Omit EFI_LOAD_FILE2_PROTOCOL for a zero-length initrd"), the choice in Linux of using a fixed device path requires bootloaders to allow for the fact that a previous bootloader may have already installed a handle with the fixed device path. We currently deal with this situation by reusing the existing handle, replacing the EFI_LOAD_FILE2_PROTOCOL instance with our own. Simplify the code by instead uninstalling the EFI_DEVICE_PATH_PROTOCOL instance from the existing handle (if present), thereby allowing the creation of a new handle to succeed. Create the new handle only if we have a non-empty initrd to provide. This works around bugs in bootloaders such as the systemd EFI stub that fail to allow for the existence of multiple-bootloader chains. (The workaround is not comprehensive: if the user has downloaded other images in iPXE before invoking the systemd Unified Kernel Image (UKI), then the systemd EFI stub will still crash and burn since it fails to allow for the fact that a previous bootloader has already installed a handle with the fixed device path.) Signed-off-by: Michael Brown --- src/interface/efi/efi_file.c | 294 +++++++++++++++++++++++++------------------ 1 file changed, 172 insertions(+), 122 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index a0bba1606..b232591dc 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -93,8 +93,18 @@ struct efi_file { size_t ( * read ) ( struct efi_file_reader *reader ); }; +/** An EFI fixed device path file */ +struct efi_file_path { + /** EFI file */ + struct efi_file file; + /** Device path */ + EFI_DEVICE_PATH_PROTOCOL *path; + /** EFI handle */ + EFI_HANDLE handle; +}; + static struct efi_file efi_file_root; -static struct efi_file efi_file_initrd; +static struct efi_file_path efi_file_initrd; /** * Free EFI file @@ -358,8 +368,8 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, } /* Allow magic initrd to be opened */ - if ( strcasecmp ( name, efi_file_initrd.name ) == 0 ) - return efi_file_open_fixed ( &efi_file_initrd, new ); + if ( strcasecmp ( name, efi_file_initrd.file.name ) == 0 ) + return efi_file_open_fixed ( &efi_file_initrd.file, new ); /* Identify image */ image = efi_file_find ( name ); @@ -701,10 +711,10 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) { * @v data Buffer, or NULL * @ret efirc EFI status code */ -static EFI_STATUS EFIAPI efi_file_load ( EFI_LOAD_FILE2_PROTOCOL *this, - EFI_DEVICE_PATH_PROTOCOL *path __unused, - BOOLEAN boot __unused, UINTN *len, - VOID *data ) { +static EFI_STATUS EFIAPI +efi_file_load ( EFI_LOAD_FILE2_PROTOCOL *this, + EFI_DEVICE_PATH_PROTOCOL *path __unused, + BOOLEAN boot __unused, UINTN *len, VOID *data ) { struct efi_file *file = container_of ( this, struct efi_file, load ); size_t max_len; size_t file_len; @@ -752,30 +762,6 @@ static struct efi_file efi_file_root = { .name = "", }; -/** Magic initrd file */ -static struct efi_file efi_file_initrd = { - .refcnt = REF_INIT ( ref_no_free ), - .file = { - .Revision = EFI_FILE_PROTOCOL_REVISION, - .Open = efi_file_open, - .Close = efi_file_close, - .Delete = efi_file_delete, - .Read = efi_file_read, - .Write = efi_file_write, - .GetPosition = efi_file_get_position, - .SetPosition = efi_file_set_position, - .GetInfo = efi_file_get_info, - .SetInfo = efi_file_set_info, - .Flush = efi_file_flush, - }, - .load = { - .LoadFile = efi_file_load, - }, - .image = NULL, - .name = "initrd.magic", - .read = efi_file_read_initrd, -}; - /** Linux initrd fixed device path */ static struct { VENDOR_DEVICE_PATH vendor; @@ -796,6 +782,33 @@ static struct { }, }; +/** Magic initrd file */ +static struct efi_file_path efi_file_initrd = { + .file = { + .refcnt = REF_INIT ( ref_no_free ), + .file = { + .Revision = EFI_FILE_PROTOCOL_REVISION, + .Open = efi_file_open, + .Close = efi_file_close, + .Delete = efi_file_delete, + .Read = efi_file_read, + .Write = efi_file_write, + .GetPosition = efi_file_get_position, + .SetPosition = efi_file_set_position, + .GetInfo = efi_file_get_info, + .SetInfo = efi_file_set_info, + .Flush = efi_file_flush, + }, + .load = { + .LoadFile = efi_file_load, + }, + .image = NULL, + .name = "initrd.magic", + .read = efi_file_read_initrd, + }, + .path = &efi_file_initrd_path.vendor.Header, +}; + /** * Open root directory * @@ -905,107 +918,148 @@ static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = { }; /** - * (Re)install fixed device path file + * Claim use of fixed device path * - * @v path Device path - * @v load Load file protocol, or NULL to uninstall protocol + * @v file Fixed device path file * @ret rc Return status code * - * Linux 5.7 added the ability to autodetect an initrd by searching - * for a handle via a fixed vendor-specific "Linux initrd device path" - * and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on - * that handle. - * - * The design choice in Linux of using a single fixed device path - * makes this unfortunately messy to support, since device paths must - * be unique within a system. When multiple bootloaders are used - * (e.g. GRUB loading iPXE loading Linux) then only one bootloader can - * ever install the device path onto a handle. Subsequent bootloaders - * must locate the existing handle and replace the load file protocol - * instance with their own. + * The design choice in Linux of using a single fixed device path is + * unfortunately messy to support, since device paths must be unique + * within a system. When multiple bootloaders are used (e.g. GRUB + * loading iPXE loading Linux) then only one bootloader can ever + * install the device path onto a handle. Bootloaders must therefore + * be prepared to locate an existing handle and uninstall its device + * path protocol instance before installing a new handle with the + * required device path. */ -static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path, - EFI_LOAD_FILE2_PROTOCOL *load ) { +static int efi_file_path_claim ( struct efi_file_path *file ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_DEVICE_PATH_PROTOCOL *end; EFI_HANDLE handle; - VOID *path_copy; VOID *old; - size_t path_len; EFI_STATUS efirc; int rc; - /* Locate or install the handle with this device path */ - end = path; - if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid, - &end, &handle ) ) == 0 ) && - ( end->Type == END_DEVICE_PATH_TYPE ) ) { - - /* Exact match: reuse (or uninstall from) this handle */ - if ( load ) { - DBGC ( path, "EFIFILE %s reusing existing handle\n", - efi_devpath_text ( path ) ); - } + /* Sanity check */ + assert ( file->handle == NULL ); - } else { + /* Locate handle with this device path, if any */ + end = file->path; + if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid, + &end, &handle ) ) != 0 ) || + ( end->Type != END_DEVICE_PATH_TYPE ) ) { + return 0; + } - /* Allocate a permanent copy of the device path, since - * this handle will survive after this binary is - * unloaded. - */ - path_len = ( efi_path_len ( path ) + sizeof ( *end ) ); - if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, path_len, - &path_copy ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( path, "EFIFILE %s could not allocate device path: " - "%s\n", efi_devpath_text ( path ), strerror ( rc ) ); - return rc; - } - memcpy ( path_copy, path, path_len ); + /* Locate device path protocol on this handle */ + if ( ( ( efirc = bs->HandleProtocol ( handle, + &efi_device_path_protocol_guid, + &old ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( file, "EFIFILE %s could not locate %s: %s\n", + efi_file_name ( &file->file ), + efi_devpath_text ( file->path ), strerror ( rc ) ); + return rc; + } - /* Create a new handle with this device path */ - handle = NULL; - if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( - &handle, - &efi_device_path_protocol_guid, path_copy, + /* Uninstall device path protocol, leaving other protocols untouched */ + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_device_path_protocol_guid, old, NULL ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( path, "EFIFILE %s could not create handle: %s\n", - efi_devpath_text ( path ), strerror ( rc ) ); - return rc; - } + rc = -EEFI ( efirc ); + DBGC ( file, "EFIFILE %s could not claim %s: %s\n", + efi_file_name ( &file->file ), + efi_devpath_text ( file->path ), strerror ( rc ) ); + return rc; } - /* Uninstall existing load file protocol instance, if any */ - if ( ( ( efirc = bs->HandleProtocol ( handle, &efi_load_file2_protocol_guid, - &old ) ) == 0 ) && - ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( - handle, - &efi_load_file2_protocol_guid, old, - NULL ) ) != 0 ) ) { + DBGC ( file, "EFIFILE %s claimed %s", + efi_file_name ( &file->file ), efi_devpath_text ( file->path ) ); + DBGC ( file, " from %s\n", efi_handle_name ( handle ) ); + return 0; +} + +/** + * Install fixed device path file + * + * @v file Fixed device path file + * @ret rc Return status code + * + * Linux 5.7 added the ability to autodetect an initrd by searching + * for a handle via a fixed vendor-specific "Linux initrd device path" + * and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on + * that handle. + */ +static int efi_file_path_install ( struct efi_file_path *file ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + assert ( file->handle == NULL ); + + /* Create a new handle with this device path */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &file->handle, + &efi_device_path_protocol_guid, file->path, + &efi_load_file2_protocol_guid, &file->file.load, + NULL ) ) != 0 ) { rc = -EEFI ( efirc ); - DBGC ( path, "EFIFILE %s could not uninstall %s: %s\n", - efi_devpath_text ( path ), - efi_guid_ntoa ( &efi_load_file2_protocol_guid ), - strerror ( rc ) ); + DBGC ( file, "EFIFILE %s could not install %s: %s\n", + efi_file_name ( &file->file ), + efi_devpath_text ( file->path ), strerror ( rc ) ); return rc; } - /* Install new load file protocol instance, if applicable */ - if ( ( load != NULL ) && - ( ( efirc = bs->InstallMultipleProtocolInterfaces ( - &handle, - &efi_load_file2_protocol_guid, load, - NULL ) ) != 0 ) ) { + DBGC ( file, "EFIFILE %s installed as %s\n", + efi_file_name ( &file->file ), efi_devpath_text ( file->path ) ); + return 0; +} + +/** + * Uninstall fixed device path file + * + * @v file Fixed device path file + * @ret rc Return status code + */ +static void efi_file_path_uninstall ( struct efi_file_path *file ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Do nothing if file is already uninstalled */ + if ( ! file->handle ) + return; + + /* Uninstall protocols. Do this via two separate calls, in + * case another executable has already uninstalled the device + * path protocol from our handle. + */ + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + file->handle, + &efi_device_path_protocol_guid, file->path, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( file, "EFIFILE %s could not uninstall %s: %s\n", + efi_file_name ( &file->file ), + efi_devpath_text ( file->path ), strerror ( rc ) ); + /* Continue uninstalling */ + } + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + file->handle, + &efi_load_file2_protocol_guid, &file->file.load, + NULL ) ) != 0 ) { rc = -EEFI ( efirc ); - DBGC ( path, "EFIFILE %s could not install %s: %s\n", - efi_devpath_text ( path ), + DBGC ( file, "EFIFILE %s could not uninstall %s: %s\n", + efi_file_name ( &file->file ), efi_guid_ntoa ( &efi_load_file2_protocol_guid ), strerror ( rc ) ); - return rc; + /* Continue uninstalling */ } - return 0; + /* Mark handle as uninstalled */ + file->handle = NULL; } /** @@ -1016,7 +1070,6 @@ static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path, */ int efi_file_install ( EFI_HANDLE handle ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - EFI_LOAD_FILE2_PROTOCOL *load; union { EFI_DISK_IO_PROTOCOL *diskio; void *interface; @@ -1079,24 +1132,21 @@ int efi_file_install ( EFI_HANDLE handle ) { } assert ( diskio.diskio == &efi_disk_io_protocol ); - /* Install Linux initrd fixed device path file - * - * Install the device path handle unconditionally, since we - * are definitively the bootloader providing the initrd, if - * any, to the booted image. Install the load file protocol - * instance only if the initrd is non-empty, since Linux does - * not gracefully handle a zero-length initrd. - */ - load = ( have_images() ? &efi_file_initrd.load : NULL ); - if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header, - load ) ) != 0 ) { - goto err_initrd; + /* Claim Linux initrd fixed device path */ + if ( ( rc = efi_file_path_claim ( &efi_file_initrd ) ) != 0 ) + goto err_initrd_claim; + + /* Install Linux initrd fixed device path file if non-empty */ + if ( have_images() && + ( ( rc = efi_file_path_install ( &efi_file_initrd ) ) != 0 ) ) { + goto err_initrd_install; } return 0; - efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); - err_initrd: + efi_file_path_uninstall ( &efi_file_initrd ); + err_initrd_install: + err_initrd_claim: bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, efi_image_handle, handle ); err_open: @@ -1123,7 +1173,7 @@ void efi_file_uninstall ( EFI_HANDLE handle ) { int rc; /* Uninstall Linux initrd fixed device path file */ - efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); + efi_file_path_uninstall ( &efi_file_initrd ); /* Close our own disk I/O protocol */ bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, -- cgit v1.2.3-55-g7522