From d57be0b4c67c352d197b8da4dc67eddfd7f2190c Mon Sep 17 00:00:00 2001 From: Mate Kukri Date: Thu, 18 Apr 2024 10:58:30 +0100 Subject: [PATCH] loader-proto: Add support for loading files from disk to LoadImage() Currently the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL and EFI_LOAD_FILE2_PROTOCOL are supported. Signed-off-by: Mate Kukri --- include/guid.h | 3 + include/lf2.h | 76 ++++++++++++++++++++ lib/guid.c | 3 + loader-proto.c | 192 ++++++++++++++++++++++++++++++++++++++++++++----- shim.h | 2 + 5 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 include/lf2.h diff --git a/include/guid.h b/include/guid.h index e32dfc073..12c6f6dc0 100644 --- a/include/guid.h +++ b/include/guid.h @@ -40,5 +40,8 @@ extern EFI_GUID SHIM_IMAGE_LOADER_GUID; extern EFI_GUID SHIM_LOADED_IMAGE_GUID; extern EFI_GUID MOK_VARIABLE_STORE; extern EFI_GUID SECUREBOOT_EFI_NAMESPACE_GUID; +extern EFI_GUID EFI_DEVICE_PATH_GUID; +extern EFI_GUID EFI_LOADED_IMAGE_DEVICE_PATH_GUID; +extern EFI_GUID EFI_LOAD_FILE2_GUID; #endif /* SHIM_GUID_H */ diff --git a/include/lf2.h b/include/lf2.h new file mode 100644 index 000000000..b1281d27f --- /dev/null +++ b/include/lf2.h @@ -0,0 +1,76 @@ +/** @file + Load File protocol as defined in the UEFI 2.0 specification. + + Load file protocol exists to supports the addition of new boot devices, + and to support booting from devices that do not map well to file system. + Network boot is done via a LoadFile protocol. + + UEFI 2.0 can boot from any device that produces a LoadFile protocol. + + Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef __EFI_LOAD_FILE2_PROTOCOL_H__ +#define __EFI_LOAD_FILE2_PROTOCOL_H__ + +#define EFI_LOAD_FILE2_PROTOCOL_GUID \ + { \ + 0x4006c0c1, 0xfcb3, 0x403e, {0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d } \ + } + +/// +/// Protocol Guid defined by UEFI2.1. +/// +#define LOAD_FILE2_PROTOCOL EFI_LOAD_FILE2_PROTOCOL_GUID + +typedef struct _EFI_LOAD_FILE2_PROTOCOL EFI_LOAD_FILE2_PROTOCOL; + +/** + Causes the driver to load a specified file. + + @param This Protocol instance pointer. + @param FilePath The device specific path of the file to load. + @param BootPolicy Should always be FALSE. + @param BufferSize On input the size of Buffer in bytes. On output with a return + code of EFI_SUCCESS, the amount of data transferred to + Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL, + the size of Buffer required to retrieve the requested file. + @param Buffer The memory buffer to transfer the file to. IF Buffer is NULL, + then no the size of the requested file is returned in + BufferSize. + + @retval EFI_SUCCESS The file was loaded. + @retval EFI_UNSUPPORTED BootPolicy is TRUE. + @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or + BufferSize is NULL. + @retval EFI_NO_MEDIA No medium was present to load the file. + @retval EFI_DEVICE_ERROR The file was not loaded due to a device error. + @retval EFI_NO_RESPONSE The remote system did not respond. + @retval EFI_NOT_FOUND The file was not found + @retval EFI_ABORTED The file load process was manually canceled. + @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current + directory entry. BufferSize has been updated with + the size needed to complete the request. + + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_LOAD_FILE2)( + IN EFI_LOAD_FILE2_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN BOOLEAN BootPolicy, + IN OUT UINTN *BufferSize, + IN VOID *Buffer OPTIONAL + ); + +/// +/// The EFI_LOAD_FILE_PROTOCOL is a simple protocol used to obtain files from arbitrary devices. +/// +struct _EFI_LOAD_FILE2_PROTOCOL { + EFI_LOAD_FILE2 LoadFile; +}; + +#endif diff --git a/lib/guid.c b/lib/guid.c index 1dc90ca90..9f01e819c 100644 --- a/lib/guid.c +++ b/lib/guid.c @@ -39,3 +39,6 @@ EFI_GUID SHIM_IMAGE_LOADER_GUID = {0x1f492041, 0xfadb, 0x4e59, {0x9e, 0x57, 0x7c EFI_GUID SHIM_LOADED_IMAGE_GUID = {0x6e6baeb8, 0x7108, 0x4179, {0x94, 0x9d, 0xa3, 0x49, 0x34, 0x15, 0xec, 0x97 } }; EFI_GUID MOK_VARIABLE_STORE = {0xc451ed2b, 0x9694, 0x45d3, {0xba, 0xba, 0xed, 0x9f, 0x89, 0x88, 0xa3, 0x89} }; EFI_GUID SECUREBOOT_EFI_NAMESPACE_GUID = {0x77fa9abd, 0x0359, 0x4d32, {0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b} }; +EFI_GUID EFI_DEVICE_PATH_GUID = EFI_DEVICE_PATH_PROTOCOL_GUID; +EFI_GUID EFI_LOADED_IMAGE_DEVICE_PATH_GUID = EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; +EFI_GUID EFI_LOAD_FILE2_GUID = EFI_LOAD_FILE2_PROTOCOL_GUID; diff --git a/loader-proto.c b/loader-proto.c index 5c6b5330d..248a6148c 100644 --- a/loader-proto.c +++ b/loader-proto.c @@ -3,21 +3,9 @@ * loader-proto.c - shim's loader protocol * * Copyright Red Hat, Inc + * Copyright Canonical, Ltd */ -/* Chemical agents lend themselves to covert use in sabotage against - * which it is exceedingly difficult to visualize any really effective - * defense... I will not dwell upon this use of CBW because, as one - * pursues the possibilities of such covert uses, one discovers that the - * scenarios resemble that in which the components of a nuclear weapon - * are smuggled into New York City and assembled in the basement of the - * Empire State Building. - * In other words, once the possibility is recognized to exist, about - * all that one can do is worry about it. - * -- Dr. Ivan L Bennett, Jr., testifying before the Subcommittee on - * National Security Policy and Scientific Developments, November 20, - * 1969. - */ #include "shim.h" static EFI_SYSTEM_TABLE *systab; @@ -48,6 +36,131 @@ unhook_system_services(void) BS = systab->BootServices; } +/* Reviewers: Is this better than a bunch of double pointers? */ +typedef struct { + EFI_HANDLE hnd; + EFI_DEVICE_PATH *dp; + void *buffer; + size_t size; +} buffer_properties_t; + +static EFI_STATUS +try_load_from_sfs(EFI_DEVICE_PATH *dp, buffer_properties_t *bprop) +{ + EFI_STATUS status = EFI_SUCCESS; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *sfs = NULL; + EFI_FILE_HANDLE root = NULL; + EFI_FILE_HANDLE file = NULL; + + bprop->buffer = NULL; + + /* look for a handle with SFS support from the input DP */ + bprop->dp = dp; + status = BS->LocateDevicePath(&EFI_SIMPLE_FILE_SYSTEM_GUID, &bprop->dp, &bprop->hnd); + if (EFI_ERROR(status)) { + goto out; + } + + /* make sure the remaining DP portion is really a file path */ + if (DevicePathType(bprop->dp) != MEDIA_DEVICE_PATH || + DevicePathSubType(bprop->dp) != MEDIA_FILEPATH_DP) { + status = EFI_LOAD_ERROR; + goto out; + } + + /* find protocol, open the root directory, then open file */ + status = BS->HandleProtocol(bprop->hnd, &EFI_SIMPLE_FILE_SYSTEM_GUID, (void **)&sfs); + if (EFI_ERROR(status)) + goto out; + status = sfs->OpenVolume(sfs, &root); + if (EFI_ERROR(status)) + goto out; + status = root->Open(root, &file, ((FILEPATH_DEVICE_PATH *) bprop->dp)->PathName, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) + goto out; + + /* get file size */ + status = file->SetPosition(file, -1ULL); + if (EFI_ERROR(status)) + goto out; + status = file->GetPosition(file, &bprop->size); + if (EFI_ERROR(status)) + goto out; + status = file->SetPosition(file, 0); + if (EFI_ERROR(status)) + goto out; + + /* allocate buffer */ + bprop->buffer = AllocatePool(bprop->size); + if (bprop->buffer == NULL) { + status = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* read file */ + status = file->Read(file, &bprop->size, bprop->buffer); + +out: + if (EFI_ERROR(status) && bprop->buffer) + FreePool(bprop->buffer); + if (file) + file->Close(file); + if (root) + root->Close(root); + return status; +} + + +static EFI_STATUS +try_load_from_lf2(EFI_DEVICE_PATH *dp, buffer_properties_t *bprop) +{ + EFI_STATUS status = EFI_SUCCESS; + EFI_LOAD_FILE2_PROTOCOL *lf2 = NULL; + + bprop->buffer = NULL; + + /* look for a handle with LF2 support from the input DP */ + bprop->dp = dp; + status = BS->LocateDevicePath(&EFI_LOAD_FILE2_GUID, &bprop->dp, &bprop->hnd); + if (EFI_ERROR(status)) + goto out; + + /* find protocol */ + status = BS->LocateProtocol(bprop->hnd, &EFI_LOAD_FILE2_GUID, (void **) &lf2); + if (EFI_ERROR(status)) + goto out; + + /* get file size */ + bprop->size = 0; /* this shouldn't be read when Buffer=NULL but better be safe */ + status = lf2->LoadFile(lf2, bprop->dp, /*BootPolicy=*/false, &bprop->size, NULL); + /* NOTE: the spec does *not* say what is the correct returned status code + when asking for the buffer size with Bufffer=NULL. I am assuming EFI_SUCCESS + and EFI_BUFFER_TOO_SMALL are the only reasonable interpretations. + When Buffer!=0 and the input size is too small it would be EFI_BUFFER_TOO_SMALL, + but we aren't doing that. */ + if (EFI_ERROR(status) && status != EFI_BUFFER_TOO_SMALL) { + status = EFI_LOAD_ERROR; + goto out; + } + + /* allocate buffer */ + bprop->buffer = AllocatePool(bprop->size); + if (!bprop->buffer) { + status = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* read file */ + status = lf2->LoadFile(lf2, bprop->dp, /*BootPolicy=*/false, &bprop->size, bprop->buffer); + if (EFI_ERROR(status)) + goto out; + +out: + if (EFI_ERROR(status) && bprop->buffer) + FreePool(bprop->buffer); + return status; +} + static EFI_STATUS EFIAPI shim_load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle, EFI_DEVICE_PATH *FilePath, VOID *SourceBuffer, @@ -55,21 +168,52 @@ shim_load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle, { SHIM_LOADED_IMAGE *image; EFI_STATUS efi_status; + buffer_properties_t bprop; - (void)FilePath; - - if (BootPolicy || !SourceBuffer || !SourceSize) + if (BootPolicy) return EFI_UNSUPPORTED; + if (!SourceBuffer || !SourceSize) { + if (try_load_from_sfs(FilePath, &bprop) == EFI_SUCCESS) + ; + else if (try_load_from_lf2(FilePath, &bprop) == EFI_SUCCESS) + ; + else + /* no buffer given and we cannot load from this device */ + return EFI_LOAD_ERROR; + + SourceBuffer = bprop.buffer; + SourceSize = bprop.size; + } else { + bprop.buffer = NULL; + /* even if we are using a buffer, try populating the + * device_handle and file_path fields the best we can */ + bprop.dp = FilePath; + efi_status = BS->LocateDevicePath(&EFI_DEVICE_PATH_GUID, + &bprop.dp, + &bprop.hnd); + if (efi_status != EFI_SUCCESS) { + /* can't seem to pull apart this DP */ + bprop.dp = FilePath; + bprop.hnd = NULL; + } + + } + image = AllocatePool(sizeof(*image)); - if (!image) - return EFI_OUT_OF_RESOURCES; + if (!image) { + efi_status = EFI_OUT_OF_RESOURCES; + goto free_buffer; + } SetMem(image, sizeof(*image), 0); image->li.Revision = 0x1000; image->li.ParentHandle = ParentImageHandle; image->li.SystemTable = systab; + image->li.DeviceHandle = bprop.hnd; + image->li.FilePath = DuplicateDevicePath(bprop.dp); + image->loaded_image_device_path = DuplicateDevicePath(FilePath); efi_status = handle_image(SourceBuffer, SourceSize, &image->li, &image->entry_point, &image->alloc_address, @@ -81,16 +225,24 @@ shim_load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle, efi_status = BS->InstallMultipleProtocolInterfaces(ImageHandle, &SHIM_LOADED_IMAGE_GUID, image, &EFI_LOADED_IMAGE_GUID, &image->li, + &EFI_LOADED_IMAGE_DEVICE_PATH_GUID, + image->loaded_image_device_path, NULL); if (EFI_ERROR(efi_status)) goto free_alloc; + if (bprop.buffer) + FreePool(bprop.buffer); + return EFI_SUCCESS; free_alloc: BS->FreePages(image->alloc_address, image->alloc_pages); free_image: FreePool(image); +free_buffer: + if (bprop.buffer) + FreePool(bprop.buffer); return efi_status; } @@ -133,9 +285,13 @@ shim_start_image(IN EFI_HANDLE ImageHandle, OUT UINTN *ExitDataSize, BS->UninstallMultipleProtocolInterfaces(ImageHandle, &EFI_LOADED_IMAGE_GUID, image, &SHIM_LOADED_IMAGE_GUID, &image->li, + &EFI_LOADED_IMAGE_DEVICE_PATH_GUID, + image->loaded_image_device_path, NULL); BS->FreePages(image->alloc_address, image->alloc_pages); + BS->FreePool(image->li.FilePath); + BS->FreePool(image->loaded_image_device_path); FreePool(image); return efi_status; diff --git a/shim.h b/shim.h index af4672279..a60e6a270 100644 --- a/shim.h +++ b/shim.h @@ -192,6 +192,7 @@ #include "include/ucs2.h" #include "include/variables.h" #include "include/hexdump.h" +#include "include/lf2.h" #include "version.h" @@ -339,6 +340,7 @@ typedef struct { UINTN exit_data_size; jmp_buf longjmp_buf; BOOLEAN started; + EFI_DEVICE_PATH *loaded_image_device_path; } SHIM_LOADED_IMAGE; #endif /* SHIM_H_ */