Compare commits

..

3 Commits

Author SHA1 Message Date
7d27190e99 [efi] Attempt to fetch autoexec script via TFTP
Attempt to fetch the autoexec.ipxe script via TFTP using the PXE base
code protocol installed on the loaded image's device handle, if
present.

This provides a generic alternative to the use of an embedded script
for chainloaded binaries, which is particularly useful in a UEFI
Secure Boot environment since it allows the script to be modified
without the need to sign a new binary.

As a side effect, this also provides a third method for breaking the
PXE chainloading loop (as an alternative to requiring an embedded
script or custom DHCP server configuration).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2022-01-17 16:17:17 +00:00
461e02d371 [efi] Allow for autoexec scripts that are not located in a filesystem
Signed-off-by: Michael Brown <mcb30@ipxe.org>
2022-01-17 16:14:53 +00:00
8e8c2188da [pxe] Allow cached DHCP settings to be fetched before registration
Register cached DHCP settings in a placeholder standalone settings
block, to allow settings to be fetched during early initialisation
code (before the startup code that registers them into the root
settings hierarchy).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2022-01-17 16:06:30 +00:00
4 changed files with 224 additions and 22 deletions

View File

@ -67,8 +67,22 @@ static struct cached_dhcp_packet *cached_packets[] = {
&cached_pxebs,
};
/** Cached DHCP settings placeholder block */
struct generic_settings cachedhcp_generic = {
.settings = {
.refcnt = NULL,
.name = "cachedhcp",
.siblings =
LIST_HEAD_INIT ( cachedhcp_generic.settings.siblings ),
.children =
LIST_HEAD_INIT ( cachedhcp_generic.settings.children ),
.op = &generic_settings_operations,
},
.list = LIST_HEAD_INIT ( cachedhcp_generic.list ),
};
/** Colour for debug messages */
#define colour &cached_dhcpack
#define colour &cachedhcp_settings
/**
* Free cached DHCP packet
@ -114,6 +128,9 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache,
/* Select appropriate parent settings block */
settings = ( netdev ? netdev_settings ( netdev ) : NULL );
/* Unregister from placeholder settings block */
unregister_settings ( &cache->dhcppkt->settings );
/* Register settings */
if ( ( rc = register_settings ( &cache->dhcppkt->settings, settings,
cache->name ) ) != 0 ) {
@ -143,6 +160,7 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
struct dhcphdr *dhcphdr;
unsigned int i;
size_t len;
int rc;
/* Free any existing cached packet */
cachedhcp_free ( cache );
@ -188,6 +206,14 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
}
}
/* Register in placeholder settings block */
if ( ( rc = register_settings ( &dhcppkt->settings, &cachedhcp_settings,
cache->name ) ) != 0 ) {
DBGC ( colour, "CACHEDHCP %s could not register placeholder "
"settings: %s\n", cache->name, strerror ( rc ) );
return rc;
}
/* Store as cached packet */
DBGC ( colour, "CACHEDHCP %s at %#08lx+%#zx/%#zx\n", cache->name,
user_to_phys ( data, 0 ), len, max_len );

View File

@ -526,6 +526,10 @@ void unregister_settings ( struct settings *settings ) {
DBGC ( settings, "Settings %p (\"%s\") unregistered\n",
settings, settings_name ( settings ) );
/* Do nothing more if settings are already unregistered */
if ( ! settings->parent )
return;
/* Remove from list of settings */
ref_put ( settings->parent->refcnt );
settings->parent = NULL;

View File

@ -17,6 +17,9 @@ struct cached_dhcp_packet;
extern struct cached_dhcp_packet cached_dhcpack;
extern struct cached_dhcp_packet cached_proxydhcp;
extern struct cached_dhcp_packet cached_pxebs;
extern struct generic_settings cachedhcp_generic;
#define cachedhcp_settings cachedhcp_generic.settings
extern int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
size_t max_len );

View File

@ -24,11 +24,16 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ipxe/image.h>
#include <ipxe/init.h>
#include <ipxe/in.h>
#include <ipxe/settings.h>
#include <ipxe/cachedhcp.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_autoexec.h>
#include <ipxe/efi/Protocol/PxeBaseCode.h>
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
#include <ipxe/efi/Guid/FileInfo.h>
@ -39,10 +44,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
/** Autoexec script filename */
#define AUTOEXEC_FILENAME L"autoexec.ipxe"
static wchar_t efi_autoexec_wname[] = L"autoexec.ipxe";
/** Autoexec script image name */
#define AUTOEXEC_NAME "autoexec.ipxe"
static char efi_autoexec_name[] = "autoexec.ipxe";
/** Autoexec script (if any) */
static void *efi_autoexec;
@ -51,21 +56,21 @@ static void *efi_autoexec;
static size_t efi_autoexec_len;
/**
* Load autoexec script
* Load autoexec script from filesystem
*
* @v device Device handle
* @ret rc Return status code
*/
int efi_autoexec_load ( EFI_HANDLE device ) {
static int efi_autoexec_filesystem ( EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
static wchar_t name[] = AUTOEXEC_FILENAME;
union {
void *interface;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs;
} u;
struct {
EFI_FILE_INFO info;
CHAR16 name[ sizeof ( name ) / sizeof ( name[0] ) ];
CHAR16 name[ sizeof ( efi_autoexec_wname ) /
sizeof ( efi_autoexec_wname[0] ) ];
} info;
EFI_FILE_PROTOCOL *root;
EFI_FILE_PROTOCOL *file;
@ -74,10 +79,6 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
EFI_STATUS efirc;
int rc;
/* Sanity check */
assert ( efi_autoexec == NULL );
assert ( efi_autoexec_len == 0 );
/* Open simple file system protocol */
if ( ( efirc = bs->OpenProtocol ( device,
&efi_simple_file_system_protocol_guid,
@ -99,11 +100,12 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
}
/* Open autoexec script */
if ( ( efirc = root->Open ( root, &file, name,
if ( ( efirc = root->Open ( root, &file, efi_autoexec_wname,
EFI_FILE_MODE_READ, 0 ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s has no %ls: %s\n",
efi_handle_name ( device ), name, strerror ( rc ) );
efi_handle_name ( device ), efi_autoexec_wname,
strerror ( rc ) );
goto err_open;
}
@ -113,7 +115,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
&info ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not get %ls info: %s\n",
efi_handle_name ( device ), name, strerror ( rc ) );
efi_handle_name ( device ), efi_autoexec_wname,
strerror ( rc ) );
goto err_getinfo;
}
size = info.info.FileSize;
@ -122,7 +125,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
if ( ! size ) {
rc = -EINVAL;
DBGC ( device, "EFI %s has zero-length %ls\n",
efi_handle_name ( device ), name );
efi_handle_name ( device ), efi_autoexec_wname );
goto err_empty;
}
@ -131,7 +134,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
&data ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not allocate %ls: %s\n",
efi_handle_name ( device ), name, strerror ( rc ) );
efi_handle_name ( device ), efi_autoexec_wname,
strerror ( rc ) );
goto err_alloc;
}
@ -139,7 +143,8 @@ int efi_autoexec_load ( 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 ), name, strerror ( rc ) );
efi_handle_name ( device ), efi_autoexec_wname,
strerror ( rc ) );
goto err_read;
}
@ -148,7 +153,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
efi_autoexec_len = size;
data = NULL;
DBGC ( device, "EFI %s found %ls\n",
efi_handle_name ( device ), name );
efi_handle_name ( device ), efi_autoexec_wname );
/* Success */
rc = 0;
@ -169,6 +174,170 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
return rc;
}
/**
* Load autoexec script from TFTP server
*
* @v device Device handle
* @ret rc Return status code
*/
static int efi_autoexec_tftp ( EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
union {
void *interface;
EFI_PXE_BASE_CODE_PROTOCOL *pxe;
} u;
union {
struct in_addr in;
EFI_IP_ADDRESS ip;
} server;
struct settings *settings;
size_t filename_len;
char *filename;
char *sep;
int len;
UINT64 size;
VOID *data;
EFI_STATUS efirc;
int rc;
/* Open PXE base code protocol */
if ( ( efirc = bs->OpenProtocol ( device,
&efi_pxe_base_code_protocol_guid,
&u.interface, efi_image_handle,
device,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s has no PXE base code instance: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_pxe;
}
/* Identify settings block containing cached filename, if any */
len = fetch_setting ( &cachedhcp_settings, &filename_setting,
&settings, NULL, NULL, 0 );
if ( len < 0 ) {
rc = len;
DBGC ( device, "EFI %s has no PXE boot filename: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_settings;
}
/* Allocate filename */
filename_len = ( len + ( sizeof ( efi_autoexec_name ) - 1 /* NUL */ )
+ 1 /* NUL */ );
filename = malloc ( filename_len );
if ( ! filename ) {
rc = -ENOMEM;
goto err_filename;
}
/* Fetch filename and TFTP server address */
fetch_string_setting ( settings, &filename_setting, filename,
filename_len );
memset ( &server, 0, sizeof ( server ) );
fetch_ipv4_setting ( settings, &next_server_setting, &server.in );
/* Update filename to autoexec script name */
sep = strrchr ( filename, '/' );
if ( ! sep )
sep = strrchr ( filename, '\\' );
if ( ! sep )
sep = ( filename - 1 );
strcpy ( ( sep + 1 ), efi_autoexec_name );
/* Get file size */
if ( ( efirc = u.pxe->Mtftp ( u.pxe,
EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
NULL, FALSE, &size, NULL, &server.ip,
( ( UINT8 * ) filename ), NULL,
FALSE ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not get size of %s:%s: %s\n",
efi_handle_name ( device ), inet_ntoa ( server.in ),
filename, strerror ( rc ) );
goto err_size;
}
/* Ignore zero-length files */
if ( ! size ) {
rc = -EINVAL;
DBGC ( device, "EFI %s has zero-length %s:%s\n",
efi_handle_name ( device ), inet_ntoa ( server.in ),
filename );
goto err_empty;
}
/* Allocate temporary copy */
if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size,
&data ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not allocate %s:%s: %s\n",
efi_handle_name ( device ), inet_ntoa ( server.in ),
filename, strerror ( rc ) );
goto err_alloc;
}
/* Download file */
if ( ( efirc = u.pxe->Mtftp ( u.pxe, EFI_PXE_BASE_CODE_TFTP_READ_FILE,
data, FALSE, &size, NULL, &server.ip,
( ( UINT8 * ) filename ), NULL,
FALSE ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not download %s:%s: %s\n",
efi_handle_name ( device ), inet_ntoa ( server.in ),
filename, strerror ( rc ) );
goto err_download;
}
/* Record autoexec script */
efi_autoexec = data;
efi_autoexec_len = size;
data = NULL;
DBGC ( device, "EFI %s found %s:%s\n", efi_handle_name ( device ),
inet_ntoa ( server.in ), filename );
/* Success */
rc = 0;
err_download:
if ( data )
bs->FreePool ( data );
err_alloc:
err_empty:
err_size:
free ( filename );
err_filename:
err_settings:
bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid,
efi_image_handle, device );
err_pxe:
return rc;
}
/**
* Load autoexec script
*
* @v device Device handle
* @ret rc Return status code
*/
int efi_autoexec_load ( EFI_HANDLE device ) {
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 )
return 0;
/* Try loading via TFTP, if supported */
if ( ( rc = efi_autoexec_tftp ( device ) ) == 0 )
return 0;
return -ENOENT;
}
/**
* Register autoexec script
*
@ -176,7 +345,6 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
static void efi_autoexec_startup ( void ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_HANDLE device = efi_loaded_image->DeviceHandle;
const char *name = AUTOEXEC_NAME;
struct image *image;
/* Do nothing if we have no autoexec script */
@ -184,15 +352,16 @@ static void efi_autoexec_startup ( void ) {
return;
/* Create autoexec image */
image = image_memory ( name, virt_to_user ( efi_autoexec ),
image = image_memory ( efi_autoexec_name,
virt_to_user ( efi_autoexec ),
efi_autoexec_len );
if ( ! image ) {
DBGC ( device, "EFI %s could not create %s\n",
efi_handle_name ( device ), name );
efi_handle_name ( device ), efi_autoexec_name );
return;
}
DBGC ( device, "EFI %s registered %s\n",
efi_handle_name ( device ), name );
efi_handle_name ( device ), efi_autoexec_name );
/* Free temporary copy */
bs->FreePool ( efi_autoexec );