Compare commits

...

4 Commits
8021x ... shim

Author SHA1 Message Date
4b7ad95261 WIP - shim 2023-03-13 13:39:16 +00:00
99aad39037 WIP - shim 2023-03-10 16:50:16 +00:00
b123d32870 WIP - shim 2023-03-10 15:36:08 +00:00
2aef08b7a5 WIP - shim 2023-03-07 14:19:01 +00:00
10 changed files with 373 additions and 40 deletions

View File

@ -290,6 +290,9 @@ REQUIRE_OBJECT ( cert_cmd );
#ifdef IMAGE_MEM_CMD
REQUIRE_OBJECT ( image_mem_cmd );
#endif
#ifdef SHIM_CMD
REQUIRE_OBJECT ( shim_cmd );
#endif
/*
* Drag in miscellaneous objects

View File

@ -47,6 +47,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define USB_BLOCK /* USB block devices */
#define REBOOT_CMD /* Reboot command */
#define SHIM_CMD /* EFI shim command */
#if defined ( __i386__ ) || defined ( __x86_64__ )
#define IOAPI_X86

View File

@ -160,6 +160,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
//#define CERT_CMD /* Certificate management commands */
//#define IMAGE_MEM_CMD /* Read memory command */
#define IMAGE_ARCHIVE_CMD /* Archive image management commands */
//#define SHIM_CMD /* EFI shim command */
/*
* ROM-specific options

108
src/hci/commands/shim_cmd.c Normal file
View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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 );
#include <getopt.h>
#include <ipxe/command.h>
#include <ipxe/parseopt.h>
#include <ipxe/efi/efi_shim.h>
#include <usr/imgmgmt.h>
#include <usr/shimmgmt.h>
/** @file
*
* EFI shim command
*
*/
/** "shim" options */
struct shim_options {
/** Keep original image */
int keep;
/** Download timeout */
unsigned long timeout;
/** Second stage alternative name */
char *altname;
};
/** "shim" option list */
static struct option_descriptor shim_opts[] = {
OPTION_DESC ( "keep", 'k', no_argument,
struct shim_options, keep, parse_flag ),
OPTION_DESC ( "timeout", 't', required_argument,
struct shim_options, timeout, parse_timeout ),
OPTION_DESC ( "altname", 'a', required_argument,
struct shim_options, altname, parse_string ),
};
/** "shim" command descriptor */
static struct command_descriptor shim_cmd =
COMMAND_DESC ( struct shim_options, shim_opts, 0, 1, NULL );
/**
* The "shim" command
*
* @v argc Argument count
* @v argv Argument list
* @ret rc Return status code
*/
static int shim_exec ( int argc, char **argv ) {
struct shim_options opts;
struct image *image = NULL;
char *name_uri = NULL;
int rc;
/* Parse options */
if ( ( rc = parse_options ( argc, argv, &shim_cmd, &opts ) ) != 0 )
goto err_parse;
/* Parse name/URI string */
name_uri = argv[optind];
/* Acquire image, if applicable */
if ( name_uri &&
( rc = imgacquire ( name_uri, opts.timeout, &image ) ) != 0 ) {
goto err_acquire;
}
/* (Un)register as shim */
if ( ( rc = shim ( image, opts.altname ) ) != 0 )
goto err_shim;
/* Unregister original image unless --keep was specified */
if ( image && ( ! opts.keep ) )
unregister_image ( image );
err_shim:
err_acquire:
err_parse:
return rc;
}
/** Shim commands */
struct command shim_commands[] __command = {
{
.name = "shim",
.exec = shim_exec,
},
};

View File

@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/efi/efi_wrap.h>
#include <ipxe/efi/efi_pxe.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_shim.h>
#include <ipxe/image.h>
#include <ipxe/init.h>
#include <ipxe/features.h>
@ -55,6 +56,9 @@ FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
"Could not start image" )
#define EEFI_START( efirc ) EPLATFORM ( EINFO_EEFI_START, efirc )
/** Registered shim, if any */
struct image *efi_shim;
/**
* Create device path for image
*
@ -104,20 +108,26 @@ efi_image_path ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) {
/**
* Create command line for image
*
* @v image EFI image
* @v image EFI image
* @v prefix Command line prefix, or NULL
* @ret cmdline Command line, or NULL on failure
*/
static wchar_t * efi_image_cmdline ( struct image *image ) {
static wchar_t * efi_image_cmdline ( struct image *image,
const char *prefix ) {
wchar_t *cmdline;
size_t len;
len = ( strlen ( image->name ) +
len = ( ( prefix ?
( strlen ( prefix ) + 1 /* NUL */ + 1 /* " " */ ) : 0 ) +
strlen ( image->name ) +
( image->cmdline ?
( 1 /* " " */ + strlen ( image->cmdline ) ) : 0 ) );
cmdline = zalloc ( ( len + 1 /* NUL */ ) * sizeof ( wchar_t ) );
if ( ! cmdline )
return NULL;
efi_snprintf ( cmdline, ( len + 1 /* NUL */ ), "%s%s%s",
efi_snprintf ( cmdline, ( len + 1 /* NUL */ ), "%s%s%s%s%s",
( prefix ? prefix : "" ),
( prefix ? " " : "" ),
image->name,
( image->cmdline ? " " : "" ),
( image->cmdline ? image->cmdline : "" ) );
@ -132,12 +142,16 @@ static wchar_t * efi_image_cmdline ( struct image *image ) {
*/
static int efi_image_exec ( struct image *image ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
struct image *shim = efi_shim;
struct efi_snp_device *snpdev;
EFI_DEVICE_PATH_PROTOCOL *path;
union {
EFI_LOADED_IMAGE_PROTOCOL *image;
void *interface;
} loaded;
struct image *exec;
const char *prefix;
const char *altname;
EFI_HANDLE handle;
EFI_MEMORY_TYPE type;
wchar_t *cmdline;
@ -153,15 +167,29 @@ static int efi_image_exec ( struct image *image ) {
goto err_no_snpdev;
}
/* Choose to execute image via shim if applicable */
if ( shim ) {
exec = shim;
prefix = shim->name;
altname = shim->cmdline;
DBGC ( image, "EFIIMAGE %s (aka %s) executing via %s\n",
image->name, altname, shim->name );
} else {
exec = image;
prefix = NULL;
altname = NULL;
}
/* Install file I/O protocols */
if ( ( rc = efi_file_install ( snpdev->handle ) ) != 0 ) {
if ( ( rc = efi_file_install ( snpdev->handle, image, altname ) ) != 0){
DBGC ( image, "EFIIMAGE %s could not install file protocol: "
"%s\n", image->name, strerror ( rc ) );
goto err_file_install;
}
/* Install PXE base code protocol */
if ( ( rc = efi_pxe_install ( snpdev->handle, snpdev->netdev ) ) != 0 ){
/* Install PXE base code protocol (unless using a shim) */
if ( ( ! shim ) &&
( rc = efi_pxe_install ( snpdev->handle, snpdev->netdev ) ) != 0 ){
DBGC ( image, "EFIIMAGE %s could not install PXE protocol: "
"%s\n", image->name, strerror ( rc ) );
goto err_pxe_install;
@ -175,7 +203,7 @@ static int efi_image_exec ( struct image *image ) {
}
/* Create device path for image */
path = efi_image_path ( image, snpdev->path );
path = efi_image_path ( exec, snpdev->path );
if ( ! path ) {
DBGC ( image, "EFIIMAGE %s could not create device path\n",
image->name );
@ -184,7 +212,7 @@ static int efi_image_exec ( struct image *image ) {
}
/* Create command line for image */
cmdline = efi_image_cmdline ( image );
cmdline = efi_image_cmdline ( image, prefix );
if ( ! cmdline ) {
DBGC ( image, "EFIIMAGE %s could not create command line\n",
image->name );
@ -195,8 +223,8 @@ static int efi_image_exec ( struct image *image ) {
/* Attempt loading image */
handle = NULL;
if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path,
user_to_virt ( image->data, 0 ),
image->len, &handle ) ) != 0 ) {
user_to_virt ( exec->data, 0 ),
exec->len, &handle ) ) != 0 ) {
/* Not an EFI image */
rc = -EEFI_LOAD ( efirc );
DBGC ( image, "EFIIMAGE %s could not load: %s\n",
@ -292,7 +320,8 @@ static int efi_image_exec ( struct image *image ) {
err_image_path:
efi_download_uninstall ( snpdev->handle );
err_download_install:
efi_pxe_uninstall ( snpdev->handle );
if ( ! shim )
efi_pxe_uninstall ( snpdev->handle );
err_pxe_install:
efi_file_uninstall ( snpdev->handle );
err_file_install:

View File

@ -9,7 +9,10 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
extern int efi_file_install ( EFI_HANDLE handle );
struct image;
extern int efi_file_install ( EFI_HANDLE handle, struct image *second,
const char *altname );
extern void efi_file_uninstall ( EFI_HANDLE handle );
#endif /* _IPXE_EFI_FILE_H */

View File

@ -0,0 +1,16 @@
#ifndef _IPXE_EFI_SHIM_H
#define _IPXE_EFI_SHIM_H
/** @file
*
* EFI shim
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
struct image;
extern struct image *efi_shim;
#endif /* _IPXE_EFI_SHIM_H */

View File

@ -0,0 +1,16 @@
#ifndef _USR_SHIMMGMT_H
#define _USR_SHIMMGMT_H
/** @file
*
* EFI shim management
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/image.h>
extern int shim ( struct image *image, const char *altname );
#endif /* _USR_SHIMMGMT_H */

View File

@ -94,6 +94,7 @@ struct efi_file {
};
static struct efi_file efi_file_root;
static struct efi_file efi_file_second;
static struct efi_file efi_file_initrd;
/**
@ -117,7 +118,15 @@ static void efi_file_free ( struct refcnt *refcnt ) {
*/
static const char * efi_file_name ( struct efi_file *file ) {
return ( file == &efi_file_root ? "<root>" : file->name );
if ( file == &efi_file_root ) {
return "<root>";
} else if ( file->image != NULL ) {
return file->image->name;
} else if ( file->name != NULL ) {
return file->name;
} else {
return "<UNKNOWN>";
}
}
/**
@ -281,10 +290,11 @@ static size_t efi_file_read_initrd ( struct efi_file_reader *reader ) {
* Open fixed file
*
* @v file EFI file
* @v wname Filename
* @v new New EFI file
* @ret efirc EFI status code
*/
static EFI_STATUS efi_file_open_fixed ( struct efi_file *file,
static EFI_STATUS efi_file_open_fixed ( struct efi_file *file, const char *name,
EFI_FILE_PROTOCOL **new ) {
/* Increment reference count */
@ -293,7 +303,8 @@ static EFI_STATUS efi_file_open_fixed ( struct efi_file *file,
/* Return opened file */
*new = &file->file;
DBGC ( file, "EFIFILE %s opened\n", efi_file_name ( file ) );
DBGC ( file, "EFIFILE %s opened via %s\n",
efi_file_name ( file ), name );
return 0;
}
@ -310,6 +321,33 @@ static void efi_file_image ( struct efi_file *file, struct image *image ) {
file->read = efi_file_read_image;
}
/**
* Open dynamically allocated file
*
* @v image Image
* @v new New EFI file
* @ret efirc EFI status code
*/
static EFI_STATUS efi_file_open_dynamic ( struct image *image,
EFI_FILE_PROTOCOL **new ) {
struct efi_file *file;
/* Allocate and initialise file */
file = zalloc ( sizeof ( *file ) );
if ( ! file )
return EFI_OUT_OF_RESOURCES;
ref_init ( &file->refcnt, efi_file_free );
memcpy ( &file->file, &efi_file_root.file,
sizeof ( file->file ) );
memcpy ( &file->load, &efi_file_root.load,
sizeof ( file->load ) );
efi_file_image ( file, image_get ( image ) );
*new = &file->file;
DBGC ( file, "EFIFILE %s opened\n", efi_file_name ( file ) );
return 0;
}
/**
* Open file
*
@ -325,7 +363,6 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
CHAR16 *wname, UINT64 mode, UINT64 attributes __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
char buf[ wcslen ( wname ) + 1 /* NUL */ ];
struct efi_file *new_file;
struct image *image;
char *name;
@ -341,7 +378,7 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
/* Allow root directory itself to be opened */
if ( ( name[0] == '\0' ) || ( name[0] == '.' ) )
return efi_file_open_fixed ( &efi_file_root, new );
return efi_file_open_fixed ( &efi_file_root, name, new );
/* Fail unless opening from the root */
if ( file != &efi_file_root ) {
@ -359,29 +396,22 @@ 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 );
return efi_file_open_fixed ( &efi_file_initrd, name, new );
/* Identify image */
image = efi_file_find ( name );
if ( ! image ) {
DBGC ( file, "EFIFILE %s does not exist\n", name );
return EFI_NOT_FOUND;
/* Allow registered images to be opened */
if ( ( image = efi_file_find ( name ) ) != NULL )
return efi_file_open_dynamic ( image, new );
/* Allow magic second stage to be opened */
if ( ( efi_file_second.image != NULL ) &&
( ( strcasecmp ( name, efi_file_second.image->name ) == 0 ) ||
( ( efi_file_second.name != NULL ) &&
( strcasecmp ( name, efi_file_second.name ) == 0 ) ) ) ) {
return efi_file_open_fixed ( &efi_file_second, name, new );
}
/* Allocate and initialise file */
new_file = zalloc ( sizeof ( *new_file ) );
if ( ! new_file )
return EFI_OUT_OF_RESOURCES;
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 ) );
return 0;
DBGC ( file, "EFIFILE %s does not exist\n", name );
return EFI_NOT_FOUND;
}
/**
@ -752,6 +782,30 @@ static struct efi_file efi_file_root = {
.name = "",
};
/** Magic second stage file */
static struct efi_file efi_file_second = {
.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 = NULL,
.read = efi_file_read_image,
};
/** Magic initrd file */
static struct efi_file efi_file_initrd = {
.refcnt = REF_INIT ( ref_no_free ),
@ -808,7 +862,7 @@ efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused,
EFI_FILE_PROTOCOL **file ) {
DBGC ( &efi_file_root, "EFIFILE open volume\n" );
return efi_file_open_fixed ( &efi_file_root, file );
return efi_file_open_fixed ( &efi_file_root, "volume", file );
}
/** EFI simple file system protocol */
@ -1012,9 +1066,12 @@ static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path,
* Install EFI simple file system protocol
*
* @v handle EFI handle
* @v second Second stage image, or NULL
* @v altname Second stage alternative filename, or NULL
* @ret rc Return status code
*/
int efi_file_install ( EFI_HANDLE handle ) {
int efi_file_install ( EFI_HANDLE handle, struct image *second,
const char *altname ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_LOAD_FILE2_PROTOCOL *load;
union {
@ -1079,6 +1136,12 @@ int efi_file_install ( EFI_HANDLE handle ) {
}
assert ( diskio.diskio == &efi_disk_io_protocol );
/* Initialise magic second stage file, if any */
if ( second ) {
efi_file_second.image = image_get ( second );
efi_file_second.name = altname;
}
/* Install Linux initrd fixed device path file
*
* Install the device path handle unconditionally, since we
@ -1097,6 +1160,9 @@ int efi_file_install ( EFI_HANDLE handle ) {
efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL );
err_initrd:
image_put ( efi_file_second.image );
efi_file_second.image = NULL;
efi_file_second.name = NULL;
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
efi_image_handle, handle );
err_open:
@ -1125,6 +1191,11 @@ void efi_file_uninstall ( EFI_HANDLE handle ) {
/* Uninstall Linux initrd fixed device path file */
efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL );
/* Clear magic second stage, if any */
image_put ( efi_file_second.image );
efi_file_second.image = NULL;
efi_file_second.name = NULL;
/* Close our own disk I/O protocol */
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
efi_image_handle, handle );

85
src/usr/shimmgmt.c Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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 );
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <assert.h>
#include <ipxe/image.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_shim.h>
#include <usr/shimmgmt.h>
/** @file
*
* EFI shim management
*
*/
/**
* Set shim image
*
* @v image Shim image, or NULL to clear shim
* @v altname Second stage alternative name, or NULL to use default
* @ret rc Return status code
*/
int shim ( struct image *image, const char *altname ) {
static wchar_t wbootpath[] = EFI_REMOVABLE_MEDIA_FILE_NAME;
char bootpath[ sizeof ( wbootpath ) / sizeof ( wbootpath[0] ) ];
char *bootname;
char *sep;
int rc;
/* Clear any existing shim */
image_put ( efi_shim );
efi_shim = NULL;
/* Do nothing more unless a shim is specified */
if ( ! image )
return 0;
/* Construct default second stage alternative name */
snprintf ( bootpath, sizeof ( bootpath ), "%ls", wbootpath );
sep = strrchr ( bootpath, '\\' );
assert ( sep != NULL );
bootname = ( sep + 1 );
assert ( strncasecmp ( bootname, "BOOT", 4 ) == 0 );
memcpy ( bootname, "GRUB", 4 );
/* Use default second stage alternative name, if not specified */
if ( ! altname )
altname = bootname;
/* Record second stage alternative name, if any */
if ( ( rc = image_set_cmdline ( image, altname ) ) != 0 )
return rc;
/* Record as shim */
efi_shim = image_get ( image );
DBGC ( image, "SHIM %s installed (altname %s)\n",
image->name, image->cmdline );
return 0;
}