diff options
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | cmd/bootefi.c | 14 | ||||
-rw-r--r-- | doc/README.iscsi | 159 | ||||
-rw-r--r-- | include/efi.h | 2 | ||||
-rw-r--r-- | lib/efi_loader/Kconfig | 4 | ||||
-rw-r--r-- | lib/efi_loader/efi_bootmgr.c | 6 | ||||
-rw-r--r-- | lib/efi_loader/efi_boottime.c | 42 | ||||
-rw-r--r-- | lib/vsprintf.c | 3 | ||||
-rw-r--r-- | test/print_ut.c | 4 |
9 files changed, 208 insertions, 27 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 0c7efde5a5..0aecc18a6c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -289,6 +289,7 @@ EFI PAYLOAD M: Alexander Graf <agraf@suse.de> S: Maintained T: git git://github.com/agraf/u-boot.git +F: doc/README.iscsi F: include/efi* F: lib/efi*/ F: test/py/tests/test_efi* diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 51213c0293..4233d36b72 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -126,8 +126,9 @@ static void *copy_fdt(void *fdt) static efi_status_t efi_do_enter( efi_handle_t image_handle, struct efi_system_table *st, - asmlinkage ulong (*entry)(efi_handle_t image_handle, - struct efi_system_table *st)) + EFIAPI efi_status_t (*entry)( + efi_handle_t image_handle, + struct efi_system_table *st)) { efi_status_t ret = EFI_LOAD_ERROR; @@ -138,7 +139,7 @@ static efi_status_t efi_do_enter( } #ifdef CONFIG_ARM64 -static efi_status_t efi_run_in_el2(asmlinkage ulong (*entry)( +static efi_status_t efi_run_in_el2(EFIAPI efi_status_t (*entry)( efi_handle_t image_handle, struct efi_system_table *st), efi_handle_t image_handle, struct efi_system_table *st) { @@ -162,8 +163,8 @@ static efi_status_t do_bootefi_exec(void *efi, void *fdt, struct efi_device_path *memdp = NULL; ulong ret; - ulong (*entry)(efi_handle_t image_handle, struct efi_system_table *st) - asmlinkage; + EFIAPI efi_status_t (*entry)(efi_handle_t image_handle, + struct efi_system_table *st); ulong fdt_pages, fdt_size, fdt_start, fdt_end; const efi_guid_t fdt_guid = EFI_FDT_GUID; bootm_headers_t img = { 0 }; @@ -372,6 +373,9 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) saddr = argv[1]; addr = simple_strtoul(saddr, NULL, 16); + /* Check that a numeric value was passed */ + if (!addr && *saddr != '0') + return CMD_RET_USAGE; if (argc > 2) { sfdt = argv[2]; diff --git a/doc/README.iscsi b/doc/README.iscsi new file mode 100644 index 0000000000..cb71c6e744 --- /dev/null +++ b/doc/README.iscsi @@ -0,0 +1,159 @@ +# iSCSI booting with U-Boot and iPXE + +## Motivation + +U-Boot has only a reduced set of supported network protocols. The focus for +network booting has been on UDP based protocols. A TCP stack and HTTP support +are expected to be integrated in 2018 together with a wget command. + +For booting a diskless computer this leaves us with BOOTP or DHCP to get the +address of a boot script. TFTP or NFS can be used to load the boot script, the +operating system kernel and the initial file system (initrd). + +These protocols are insecure. The client cannot validate the authenticity +of the contacted servers. And the server cannot verify the identity of the +client. + +Furthermore the services providing the operating system loader or kernel are +not the ones that the operating system typically will use. Especially in a SAN +environment this makes updating the operating system a hassle. After installing +a new kernel version the boot files have to be copied to the TFTP server +directory. + +The HTTPS protocol provides certificate based validation of servers. Sensitive +data like passwords can be securely transmitted. + +The iSCSI protocol is used for connecting storage attached networks. It +provides mutual authentication using the CHAP protocol. It typically runs on +a TCP transport. + +Thus a better solution than DHCP/TFTP/NFS boot would be to load a boot script +via HTTPS and to download any other files needed for booting via iSCSI from the +same target where the operating system is installed. + +An alternative to implementing these protocols in U-Boot is to use an existing +software that can run on top of U-Boot. iPXE is the "swiss army knife" of +network booting. It supports both HTTPS and iSCSI. It has a scripting engine for +fine grained control of the boot process and can provide a command shell. + +iPXE can be built as an EFI application (named snp.efi) which can be loaded and +run by U-Boot. + +## Boot sequence + +U-Boot loads the EFI application iPXE snp.efi using the bootefi command. This +application has network access via the simple network protocol offered by +U-Boot. + +iPXE executes its internal script. This script may optionally chain load a +secondary boot script via HTTPS or open a shell. + +For the further boot process iPXE connects to the iSCSI server. This includes +the mutual authentication using the CHAP protocol. After the authentication iPXE +has access to the iSCSI targets. + +For a selected iSCSI target iPXE sets up a handle with the block IO protocol. It +uses the ConnectController boot service of U-Boot to request U-Boot to connect a +file system driver. U-Boot reads from the iSCSI drive via the block IO protocol +offered by iPXE. It creates the partition handles and installs the simple file +protocol. Now iPXE can call the simple file protocol to load Grub. U-Boot uses +the block IO protocol offered by iPXE to fulfill the request. + +Once Grub is started it uses the same block IO protocol to load Linux. Via +the EFI stub Linux is called as an EFI application. + +``` + +--------+ +--------+ + | | Runs | | + | U-Boot |=========>| iPXE | + | EFI | | snp.efi| ++--------+ | | DHCP | | +| |<====|********|<=========| | +| DHCP | | | Get IP | | +| Server | | | Address | | +| |====>|********|=========>| | ++--------+ | | Response | | + | | | | + | | | | ++--------+ | | HTTPS | | +| |<====|********|<=========| | +| HTTPS | | | Load | | +| Server | | | Script | | +| |====>|********|=========>| | ++--------+ | | | | + | | | | + | | | | ++--------+ | | iSCSI | | +| |<====|********|<=========| | +| iSCSI | | | Auth | | +| Server |====>|********|=========>| | +| | | | | | +| | | | Loads | | +| |<====|********|<=========| | +--------+ +| | | | Grub | | Runs | | +| |====>|********|=========>| |=======>| Grub | +| | | | | | | | +| | | | | | | | +| | | | | | Loads | | +| |<====|********|<=========|********|<=======| | +--------+ +| | | | | | Linux | | Runs | | +| |====>|********|=========>|********|=======>| |=====>| Linux | +| | | | | | | | | | ++--------+ +--------+ +--------+ +--------+ | | + | | + | | + | ~ ~ ~ ~| +``` + +## Security + +The iSCSI protocol is not encrypted. The traffic could be secured using IPsec +but neither U-Boot nor iPXE does support this. So we should at least separate +the iSCSI traffic from all other network traffic. This can be achieved using a +virtual local area network (VLAN). + +## Configuration + +### iPXE + +For running iPXE on arm64 the bin-arm64-efi/snp.efi build target is needed. + + git clone http://git.ipxe.org/ipxe.git + cd ipxe/src + make bin-arm64-efi/snp.efi -j6 EMBED=myscript.ipxe + +The available commands for the boot script are documented at: + +http://ipxe.org/cmd + +Credentials are managed as environment variables. These are described here: + +http://ipxe.org/cfg + +iPXE by default will put the CPU to rest when waiting for input. U-Boot does +not wake it up due to missing interrupt support. To avoid this behavior create +file src/config/local/nap.h. + + /* nap.h */ + #undef NAP_EFIX86 + #undef NAP_EFIARM + #define NAP_NULL + +The supported commands in iPXE are controlled by an include, too. Putting the +following into src/config/local/general.h is sufficient for most use cases. + + /* general.h */ + #define NSLOOKUP_CMD /* Name resolution command */ + #define PING_CMD /* Ping command */ + #define NTP_CMD /* NTP commands */ + #define VLAN_CMD /* VLAN commands */ + #define IMAGE_EFI /* EFI image support */ + #define DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */ + #define DOWNLOAD_PROTO_FTP /* File Transfer Protocol */ + #define DOWNLOAD_PROTO_NFS /* Network File System Protocol */ + #define DOWNLOAD_PROTO_FILE /* Local file system access */ + +## Links + +* https://ipxe.org - iPXE open source boot firmware +* https://www.gnu.org/software/grub/ - GNU Grub (Grand Unified Bootloader) diff --git a/include/efi.h b/include/efi.h index 2f0be9c86c..98bddbac1a 100644 --- a/include/efi.h +++ b/include/efi.h @@ -19,7 +19,7 @@ #include <linux/string.h> #include <linux/types.h> -#ifdef CONFIG_EFI_STUB_64BIT +#if CONFIG_EFI_STUB_64BIT || (!defined(CONFIG_EFI_STUB) && defined(__x86_64__)) /* EFI uses the Microsoft ABI which is not the default for GCC */ #define EFIAPI __attribute__((ms_abi)) #else diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index d2b6327119..827c267b60 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -1,6 +1,10 @@ config EFI_LOADER bool "Support running EFI Applications in U-Boot" depends on (ARM || X86) && OF_LIBFDT + # We need EFI_STUB_64BIT to be set on x86_64 with EFI_STUB + depends on !EFI_STUB || !X86_64 || EFI_STUB_64BIT + # We need EFI_STUB_32BIT to be set on x86_32 with EFI_STUB + depends on !EFI_STUB || !X86 || X86_64 || EFI_STUB_32BIT default y help Select this option if you want to run EFI applications (like grub2) diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c index 857d88a879..c96b9d48c5 100644 --- a/lib/efi_loader/efi_bootmgr.c +++ b/lib/efi_loader/efi_bootmgr.c @@ -120,11 +120,9 @@ static void *try_load_entry(uint16_t n, struct efi_device_path **device_path, if (lo.attributes & LOAD_OPTION_ACTIVE) { efi_status_t ret; - u16 *str = NULL; - debug("%s: trying to load \"%ls\" from: %ls\n", __func__, - lo.label, (str = efi_dp_str(lo.file_path))); - efi_free_pool(str); + debug("%s: trying to load \"%ls\" from %pD\n", + __func__, lo.label, lo.file_path); ret = efi_load_image_from_path(lo.file_path, &image); diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 39d8511fe3..da93498b36 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -111,8 +111,11 @@ void efi_restore_gd(void) } /* - * Two spaces per indent level, maxing out at 10.. which ought to be - * enough for anyone ;-) + * Return a string for indenting with two spaces per level. A maximum of ten + * indent levels is supported. Higher indent levels will be truncated. + * + * @level indent level + * @return indent string */ static const char *indent_string(int level) { @@ -1364,16 +1367,18 @@ efi_status_t efi_setup_loaded_image( obj->handle = info; info->file_path = file_path; - if (device_path) - info->device_handle = efi_dp_find_obj(device_path, NULL); - /* - * When asking for the device path interface, return - * bootefi_device_path - */ - ret = efi_add_protocol(obj->handle, &efi_guid_device_path, device_path); - if (ret != EFI_SUCCESS) - goto failure; + if (device_path) { + info->device_handle = efi_dp_find_obj(device_path, NULL); + /* + * When asking for the device path interface, return + * bootefi_device_path + */ + ret = efi_add_protocol(obj->handle, &efi_guid_device_path, + device_path); + if (ret != EFI_SUCCESS) + goto failure; + } /* * When asking for the loaded_image interface, just @@ -1456,7 +1461,7 @@ error: * for details. * * @boot_policy true for request originating from the boot manager - * @parent_image the calles's image handle + * @parent_image the caller's image handle * @file_path the path of the image to load * @source_buffer memory location from which the image is installed * @source_size size of the memory area from which the image is @@ -1534,8 +1539,8 @@ static efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle, unsigned long *exit_data_size, s16 **exit_data) { - asmlinkage ulong (*entry)(efi_handle_t image_handle, - struct efi_system_table *st); + EFIAPI efi_status_t (*entry)(efi_handle_t image_handle, + struct efi_system_table *st); struct efi_loaded_image *info = image_handle; efi_status_t ret; @@ -1575,8 +1580,13 @@ static efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle, ret = EFI_CALL(entry(image_handle, &systab)); - /* Should usually never get here */ - return EFI_EXIT(ret); + /* + * Usually UEFI applications call Exit() instead of returning. + * But because the world doesn not consist of ponies and unicorns, + * we're happy to emulate that behavior on behalf of a payload + * that forgot. + */ + return EFI_CALL(systab.boottime->exit(image_handle, ret, 0, NULL)); } /* diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 226f4eb3e5..5f7a5f17dc 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -300,8 +300,9 @@ static char *device_path_string(char *buf, char *end, void *dp, int field_width, { u16 *str; + /* If dp == NULL output the string '<NULL>' */ if (!dp) - return "<NULL>"; + return string16(buf, end, dp, field_width, precision, flags); str = efi_dp_str((struct efi_device_path *)dp); if (!str) diff --git a/test/print_ut.c b/test/print_ut.c index 1aa68be7a9..d8e9da8fa8 100644 --- a/test/print_ut.c +++ b/test/print_ut.c @@ -44,6 +44,10 @@ static void efi_ut_print(void) snprintf(str, sizeof(str), "_%pD_", buf); assert(!strcmp("_/SD(3)_", str)); + + /* NULL device path */ + snprintf(str, sizeof(str), "_%pD_", NULL); + assert(!strcmp("_<NULL>_", str)); #endif } |