mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-04-29 20:05:21 +00:00
223 lines
6.8 KiB
C
223 lines
6.8 KiB
C
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
/*
|
|
* shim - trivial UEFI first-stage bootloader
|
|
*
|
|
* 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
|
|
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_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
|
|
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;
|
|
}
|