summaryrefslogtreecommitdiff
path: root/loader-proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'loader-proto.c')
-rw-r--r--loader-proto.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/loader-proto.c b/loader-proto.c
new file mode 100644
index 00000000..a61a91db
--- /dev/null
+++ b/loader-proto.c
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+/*
+ * loader-proto.c - shim's loader protocol
+ *
+ * Copyright Red Hat, Inc
+ */
+
+/* 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;
+
+EFI_SYSTEM_TABLE *
+get_active_systab(void)
+{
+ if (systab)
+ return systab;
+ return ST;
+}
+
+static typeof(systab->BootServices->LoadImage) system_load_image;
+static typeof(systab->BootServices->StartImage) system_start_image;
+static typeof(systab->BootServices->Exit) system_exit;
+#if !defined(DISABLE_EBS_PROTECTION)
+static typeof(systab->BootServices->ExitBootServices) system_exit_boot_services;
+#endif /* !defined(DISABLE_EBS_PROTECTION) */
+
+static EFI_HANDLE last_loaded_image;
+
+void
+unhook_system_services(void)
+{
+ if (!systab)
+ return;
+
+ systab->BootServices->LoadImage = system_load_image;
+ systab->BootServices->StartImage = system_start_image;
+#if !defined(DISABLE_EBS_PROTECTION)
+ systab->BootServices->ExitBootServices = system_exit_boot_services;
+#endif /* !defined(DISABLE_EBS_PROTECTION) */
+ BS = systab->BootServices;
+}
+
+static EFI_STATUS EFIAPI
+shim_load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle,
+ EFI_DEVICE_PATH *FilePath, VOID *SourceBuffer,
+ UINTN SourceSize, EFI_HANDLE *ImageHandle)
+{
+ SHIM_LOADED_IMAGE *image;
+ EFI_STATUS efi_status;
+
+ (void)FilePath;
+
+ if (BootPolicy || !SourceBuffer || !SourceSize)
+ return EFI_UNSUPPORTED;
+
+ image = AllocatePool(sizeof(*image));
+ if (!image)
+ return EFI_OUT_OF_RESOURCES;
+
+ SetMem(image, sizeof(*image), 0);
+
+ image->li.Revision = 0x1000;
+ image->li.ParentHandle = ParentImageHandle;
+ image->li.SystemTable = systab;
+
+ efi_status = handle_image(SourceBuffer, SourceSize, &image->li,
+ &image->entry_point, &image->alloc_address,
+ &image->alloc_pages);
+ if (EFI_ERROR(efi_status))
+ goto free_image;
+
+ *ImageHandle = NULL;
+ efi_status = BS->InstallMultipleProtocolInterfaces(ImageHandle,
+ &SHIM_LOADED_IMAGE_GUID, image,
+ &EFI_LOADED_IMAGE_GUID, &image->li,
+ NULL);
+ if (EFI_ERROR(efi_status))
+ goto free_alloc;
+
+ return EFI_SUCCESS;
+
+free_alloc:
+ BS->FreePages(image->alloc_address, image->alloc_pages);
+free_image:
+ FreePool(image);
+ return efi_status;
+}
+
+static EFI_STATUS EFIAPI
+shim_start_image(IN EFI_HANDLE ImageHandle, OUT UINTN *ExitDataSize,
+ OUT CHAR16 **ExitData OPTIONAL)
+{
+ SHIM_LOADED_IMAGE *image;
+ EFI_STATUS efi_status;
+
+ efi_status = BS->HandleProtocol(ImageHandle, &SHIM_LOADED_IMAGE_GUID,
+ (void **)&image);
+ if (EFI_ERROR(efi_status) || image->started)
+ return EFI_INVALID_PARAMETER;
+
+ if (!setjmp(image->longjmp_buf)) {
+ image->started = true;
+ efi_status =
+ image->entry_point(ImageHandle, image->li.SystemTable);
+ } else {
+ if (ExitData) {
+ *ExitDataSize = image->exit_data_size;
+ *ExitData = (CHAR16 *)image->exit_data;
+ }
+ efi_status = image->exit_status;
+ }
+
+ //
+ // We only support EFI applications, so we can unload and free the
+ // image unconditionally.
+ //
+ BS->UninstallMultipleProtocolInterfaces(ImageHandle,
+ &EFI_LOADED_IMAGE_GUID, image,
+ &SHIM_LOADED_IMAGE_GUID, &image->li,
+ NULL);
+
+ BS->FreePages(image->alloc_address, image->alloc_pages);
+ FreePool(image);
+
+ return efi_status;
+}
+
+static EFI_STATUS EFIAPI
+load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle,
+ EFI_DEVICE_PATH *DevicePath, VOID *SourceBuffer,
+ UINTN SourceSize, EFI_HANDLE *ImageHandle)
+{
+ EFI_STATUS efi_status;
+
+ unhook_system_services();
+ efi_status = BS->LoadImage(BootPolicy, ParentImageHandle, DevicePath,
+ SourceBuffer, SourceSize, ImageHandle);
+ hook_system_services(systab);
+ if (EFI_ERROR(efi_status))
+ last_loaded_image = NULL;
+ else
+ last_loaded_image = *ImageHandle;
+ return efi_status;
+}
+
+static EFI_STATUS EFIAPI
+replacement_start_image(EFI_HANDLE image_handle, UINTN *exit_data_size, CHAR16 **exit_data)
+{
+ EFI_STATUS efi_status;
+ unhook_system_services();
+
+ if (image_handle == last_loaded_image) {
+ UINT8 retain_protocol = 0;
+ UINTN retain_protocol_size = sizeof(retain_protocol);
+ UINT32 retain_protocol_attrs = 0;
+
+ loader_is_participating = 1;
+
+ /* If a boot component asks us, keep our protocol around - it will be used to
+ * validate further PE payloads (e.g.: by the UKI stub, before the kernel is booted).
+ * But also check that the variable was set by a boot component, to ensure that
+ * nobody at runtime can attempt to change shim's behaviour. */
+ efi_status = RT->GetVariable(SHIM_RETAIN_PROTOCOL_VAR_NAME,
+ &SHIM_LOCK_GUID,
+ &retain_protocol_attrs,
+ &retain_protocol_size,
+ &retain_protocol);
+ if (EFI_ERROR(efi_status) ||
+ (retain_protocol_attrs & EFI_VARIABLE_NON_VOLATILE) ||
+ !(retain_protocol_attrs & EFI_VARIABLE_BOOTSERVICE_ACCESS) ||
+ retain_protocol_size != sizeof(retain_protocol) ||
+ retain_protocol == 0)
+ uninstall_shim_protocols();
+ }
+ efi_status = BS->StartImage(image_handle, exit_data_size, exit_data);
+ if (EFI_ERROR(efi_status)) {
+ if (image_handle == last_loaded_image) {
+ EFI_STATUS efi_status2 = install_shim_protocols();
+
+ if (EFI_ERROR(efi_status2)) {
+ console_print(L"Something has gone seriously wrong: %r\n",
+ efi_status2);
+ console_print(L"shim cannot continue, sorry.\n");
+ usleep(5000000);
+ RT->ResetSystem(EfiResetShutdown,
+ EFI_SECURITY_VIOLATION,
+ 0, NULL);
+ }
+ }
+ hook_system_services(systab);
+ loader_is_participating = 0;
+ }
+ return efi_status;
+}
+
+#if !defined(DISABLE_EBS_PROTECTION)
+static EFI_STATUS EFIAPI
+exit_boot_services(EFI_HANDLE image_key, UINTN map_key)
+{
+ if (loader_is_participating ||
+ verification_method == VERIFIED_BY_HASH) {
+ unhook_system_services();
+ EFI_STATUS efi_status;
+ efi_status = BS->ExitBootServices(image_key, map_key);
+ if (EFI_ERROR(efi_status))
+ hook_system_services(systab);
+ return efi_status;
+ }
+
+ console_print(L"Bootloader has not verified loaded image.\n");
+ console_print(L"System is compromised. halting.\n");
+ usleep(5000000);
+ RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL);
+ return EFI_SECURITY_VIOLATION;
+}
+#endif /* !defined(DISABLE_EBS_PROTECTION) */
+
+static EFI_STATUS EFIAPI
+do_exit(EFI_HANDLE ImageHandle, EFI_STATUS ExitStatus,
+ UINTN ExitDataSize, CHAR16 *ExitData)
+{
+ EFI_STATUS efi_status;
+ SHIM_LOADED_IMAGE *image;
+
+ efi_status = BS->HandleProtocol(ImageHandle, &SHIM_LOADED_IMAGE_GUID,
+ (void **)&image);
+ if (!EFI_ERROR(efi_status)) {
+ image->exit_status = ExitStatus;
+ image->exit_data_size = ExitDataSize;
+ image->exit_data = ExitData;
+
+ longjmp(image->longjmp_buf, 1);
+ }
+
+ shim_fini();
+
+ restore_loaded_image();
+
+ efi_status = BS->Exit(ImageHandle, ExitStatus,
+ ExitDataSize, ExitData);
+ if (EFI_ERROR(efi_status)) {
+ EFI_STATUS efi_status2 = shim_init();
+
+ if (EFI_ERROR(efi_status2)) {
+ console_print(L"Something has gone seriously wrong: %r\n",
+ efi_status2);
+ console_print(L"shim cannot continue, sorry.\n");
+ usleep(5000000);
+ RT->ResetSystem(EfiResetShutdown,
+ EFI_SECURITY_VIOLATION, 0, NULL);
+ }
+ }
+ return efi_status;
+}
+
+void
+init_image_loader(void)
+{
+ shim_image_loader_interface.LoadImage = shim_load_image;
+ shim_image_loader_interface.StartImage = shim_start_image;
+}
+
+void
+hook_system_services(EFI_SYSTEM_TABLE *local_systab)
+{
+ systab = local_systab;
+ BS = systab->BootServices;
+
+ /* We need to hook various calls to make this work... */
+
+ /* We need LoadImage() hooked so that fallback.c can load shim
+ * without having to fake LoadImage as well. This allows it
+ * to call the system LoadImage(), and have us track the output
+ * and mark loader_is_participating in replacement_start_image. This
+ * means anything added by fallback has to be verified by the system
+ * db, which we want to preserve anyway, since that's all launching
+ * through BDS gives us. */
+ system_load_image = systab->BootServices->LoadImage;
+ systab->BootServices->LoadImage = load_image;
+
+ /* we need StartImage() so that we can allow chain booting to an
+ * image trusted by the firmware */
+ system_start_image = systab->BootServices->StartImage;
+ systab->BootServices->StartImage = replacement_start_image;
+
+#if !defined(DISABLE_EBS_PROTECTION)
+ /* we need to hook ExitBootServices() so a) we can enforce the policy
+ * and b) we can unwrap when we're done. */
+ system_exit_boot_services = systab->BootServices->ExitBootServices;
+ systab->BootServices->ExitBootServices = exit_boot_services;
+#endif /* defined(DISABLE_EBS_PROTECTION) */
+}
+
+void
+unhook_exit(void)
+{
+ systab->BootServices->Exit = system_exit;
+ BS = systab->BootServices;
+}
+
+void
+hook_exit(EFI_SYSTEM_TABLE *local_systab)
+{
+ systab = local_systab;
+ BS = local_systab->BootServices;
+
+ /* we need to hook Exit() so that we can allow users to quit the
+ * bootloader and still e.g. start a new one or run an internal
+ * shell. */
+ system_exit = systab->BootServices->Exit;
+ systab->BootServices->Exit = do_exit;
+}