UEFI实战——Secure Boot

说明

Secure Boot,顾名思义就是用来保证启动安全的一套措施。

Secure Boot是一个比较普通的说法,使用的场景也很多,所以这里要特别说明一下,这里指的是UEFI BIOS下的,用来启动诸如Windows、Mac OS之类系统的“Secure Boot”。

Secure Boot最早在《UEFI Spec》2.3.1版本中提出,关于它的具体说明,可以参考下面的网站:

https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-secure-boot

https://www.intel.com/content/www/us/en/support/articles/000006942/boards-and-kits/desktop-boards.html

为什么要使用Secure Boot?

首先BIOS执行的过程中可能需要运行一些第三方的程序。比如有一张显卡,其上包含OPROM(就是BIOS下可运行的初始化程序),BIOS为了初始化这张显卡,就会从该显卡处获取到OPROM然后执行,这是一个例子,但是对于这种情况还好,即使不执行显卡的初始化,影响也不大。但是还存在另外的一个东西,是大部分系统启动所必须的,那就是GRUB。通常支持UEFI的系统在安装过程中都会将一个GRUB文件到磁盘的FAT32分区中,然后UEFI BIOS启动时会执行这个文件,并通过这个文件找到系统内核并启动操作系统。

这个GRUB也是一个第三方的程序,关于它可以参考下面的文章:

GRUB2基本操作

GRUB2编译与使用

GRUB2基础——增加自定义命令

GRUB2代码初步解析

另外,在UEFI Shell下也可以执行第三方的程序,具体可以参考UEFI基础——UEFI Shell

当我们要执行这些第三方程序时,如何保证它们的安全呢?以及当GRUB加载内核时,如何保证内核是安全的呢?

正是因为有了这些顾虑,才引入了Secure Boot。

当开启了Secure Boot功能之后,在执行第三方程序时,都会对它们进行验签,只有保证其数字签名正常,才会将控制权限交给它们。

关于验签、数字签名等概念,稍后会说明。

之后需要说明的是Secure Boot存在的问题。在搜索网站上实际上会看到很多对于“Disable Secure Boot”的咨询,这是因为Secure Boot在保证安全的同时也带来了很多麻烦。比如买了一张新的显卡,但是因为其OPROM没有有效签名,结果卡死在BIOS下面了;又比如想换个系统,结果因为该系统的GRUB没有签名,导致系统起不来了,等等。

关于如何打开和关闭Secure Boot,实际上在BIOS的配置菜单中都提供相应的开关,下面是惠普笔记本的一个例子:

从上图我们也可以看到之前遗留的一个问题,即签名的问题。

除了打开和关闭Secure Boot(Legacy不在我们的讨论中),这里还涉及到key的管理,包括使用微软的key,以及导入自定义的key,实际上在https://docs.microsoft.com/zh-cn/windows/security/information-protection/secure-the-windows-10-boot-process中也有说明:

  • 使用具有认证的启动加载程序的操作系统。 由于所有已认证的 Windows10 电脑必须信任 Microsoft 的证书,Microsoft 提供了一种服务来分析和签署任何非 Microsoft 的引导程序,以便所有认证的 Windows10 电脑能够获得信任。 事实上,已经有了能够加载 Linux 的开源启动加载程序。 若要开始获取证书的过程,请转到https://partner.microsoft.com/dashboard
  • 将 UEFI 配置为信任自定义的启动加载程序。 所有经认证的 Windows10 电脑都允许你通过将签名添加到 UEFI 数据库来信任未验证的加载程序,从而允许你运行任何操作系统,包括 homemade 操作系统。
  • 关闭安全启动。 所有经认证的 Windows10 Pc 允许您关闭安全启动,以便您可以运行任何软件。 但是,这不会帮助保护你免受 bootkit 的攻击。

默认UEFI支持微软的key,即通过微软签名的第三方程序UEFI会认为是安全的。同时我们也可以自己给第三方程序签名,然后通过使用自己的key来对第三方程序进行验签。

最后的最后,再说明下对于启动Linux的GRUB,事实上现在已经有可以支持安全启动和Linux版本的GRUB,称为shim,可以在http://mjg59.dreamwidth.org/20303.html中看到具体的说明。

实现

对于第三方的应用如何进行验签,可以从UEFI Shell下执行UEFI应用开始。

对应的代码如下(位于Shell.c中,参考代码https://gitee.com/jiangwei0512/vUDK2017):

        case   Efi_Application:
          //
          // Get the device path of the application image
          //
          DevPath = ShellInfoObject.NewEfiShellProtocol->GetDevicePathFromFilePath(CommandWithPath);
          if (DevPath == NULL){
            Status = EFI_OUT_OF_RESOURCES;
            break;
          }

          //
          // Execute the device path
          //
          Status = InternalShellExecuteDevicePath(
            &gImageHandle,
            DevPath,
            CmdLine,
            NULL,
            &StartStatus
           );

该函数内部最重要的两步如下:

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    *ParentImageHandle,
    (EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
    NULL,
    0,
    &NewHandle);

    //
    // now start the image and if the caller wanted the return code pass it to them...
    //
    if (!EFI_ERROR(Status)) {
      StartStatus      = gBS->StartImage(
                          NewHandle,
                          0,
                          NULL
                          );

但其实这里并没有出现验签相关的代码。还需要追踪到LoadImage函数中,对应的实现在MdeModulePkg\Core\Dxe\Image\Image.c:

EFI_STATUS
EFIAPI
CoreLoadImage (
  IN BOOLEAN                    BootPolicy,
  IN EFI_HANDLE                 ParentImageHandle,
  IN EFI_DEVICE_PATH_PROTOCOL   *FilePath,
  IN VOID                       *SourceBuffer   OPTIONAL,
  IN UINTN                      SourceSize,
  OUT EFI_HANDLE                *ImageHandle
  )

它的实现中有如下的代码:

  if (gSecurity2 != NULL) {
    //
    // Verify File Authentication through the Security2 Architectural Protocol
    //
    SecurityStatus = gSecurity2->FileAuthentication (
                                  gSecurity2,
                                  OriginalFilePath,
                                  FHand.Source,
                                  FHand.SourceSize,
                                  BootPolicy
                                  );
    if (!EFI_ERROR (SecurityStatus) && ImageIsFromFv) {
      //
      // When Security2 is installed, Security Architectural Protocol must be published.
      //
      ASSERT (gSecurity != NULL);

      //
      // Verify the Authentication Status through the Security Architectural Protocol
      // Only on images that have been read using Firmware Volume protocol.
      //
      SecurityStatus = gSecurity->FileAuthenticationState (
                                    gSecurity,
                                    AuthenticationStatus,
                                    OriginalFilePath
                                    );
    }
  } else if ((gSecurity != NULL) && (OriginalFilePath != NULL)) {
    //
    // Verify the Authentication Status through the Security Architectural Protocol
    //
    SecurityStatus = gSecurity->FileAuthenticationState (
                                  gSecurity,
                                  AuthenticationStatus,
                                  OriginalFilePath
                                  );
  }

这里涉及到两个Protocol,其实是一个Protocol的两个版本:

EFI_SECURITY_ARCH_PROTOCOL        *gSecurity      = NULL;
EFI_SECURITY2_ARCH_PROTOCOL       *gSecurity2     = NULL;

其中版本1是必须要的,而版本2是可选的:

//
// DXE Core Global Variables for all of the Architectural Protocols.
// If a protocol is installed mArchProtocols[].Present will be TRUE.
//
// CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event
// and mArchProtocols[].Registration as it creates events for every array
// entry.
//
EFI_CORE_PROTOCOL_NOTIFY_ENTRY  mArchProtocols[] = {
  { &gEfiSecurityArchProtocolGuid,         (VOID **)&gSecurity,      NULL, NULL, FALSE },

//
// Optional protocols that the DXE Core will use if they are present
//
EFI_CORE_PROTOCOL_NOTIFY_ENTRY  mOptionalProtocols[] = {
  { &gEfiSecurity2ArchProtocolGuid,        (VOID **)&gSecurity2,     NULL, NULL, FALSE },

上述的Protocol通过若干个库、PCD和驱动等组件来实现,如下所示(以BeniPkg为例):

PlatformSecureLib|OvmfPkg/Library/PlatformSecureLib/PlatformSecureLib.inf
TpmMeasurementLib|SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf
AuthVariableLib|SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf

gUefiOvmfPkgTokenSpaceGuid.PcdSecureBootEnable|TRUE
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00

MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf {
 <LibraryClasses>
  NULL|SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.inf
 }
SecurityPkg/VariableAuthenticated/SecureBootConfigDxe/SecureBootConfigDxe.inf

下面一一介绍这些组件。

组件介绍

本节主要介绍上面提到的支持Secure Boot的组件。

PlatformSecureLib

该库非常简单,只提供了一个接口:

/**

  This function provides a platform-specific method to detect whether the platform
  is operating by a physically present user. 

  Programmatic changing of platform security policy (such as disable Secure Boot,
  or switch between Standard/Custom Secure Boot mode) MUST NOT be possible during
  Boot Services or after exiting EFI Boot Services. Only a physically present user
  is allowed to perform these operations.

  NOTE THAT: This function cannot depend on any EFI Variable Service since they are
  not available when this function is called in AuthenticateVariable driver.
  
  @retval  TRUE       The platform is operated by a physically present user.
  @retval  FALSE      The platform is NOT operated by a physically present user.

**/
BOOLEAN
EFIAPI
UserPhysicalPresent (
  VOID
  );

如注释所说,这个函数保证对Secure Boot的相关修改必须有实际存在的人进行操作,比如说会弹出一个框,需要接键盘来选择确认,当然这个会根据不同的平台来实现,有些可能根本就不需要特别实现。

TpmMeasurementLib

它也提供了一个接口:

/**
  Tpm measure and log data, and extend the measurement result into a specific PCR.

  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
EFI_STATUS
EFIAPI
TpmMeasureAndLogData (
  IN UINT32             PcrIndex,
  IN UINT32             EventType,
  IN VOID               *EventLog,
  IN UINT32             LogLen,
  IN VOID               *HashData,
  IN UINT64             HashDataLen
  );

这个是配合TPM使用的,应该并不是必需的。

AuthVariableLib

它是对变量的扩展,在MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf中使用到这个库。比如:

  if (mVariableModuleGlobal->VariableGlobal.AuthFormat) {
    //
    // Authenticated variable initialize.
    //
    mAuthContextIn.StructSize = sizeof (AUTH_VAR_LIB_CONTEXT_IN);
    mAuthContextIn.MaxAuthVariableSize = mVariableModuleGlobal->MaxAuthVariableSize - GetVariableHeaderSize ();
    Status = AuthVariableLibInitialize (&mAuthContextIn, &mAuthContextOut);
    if (!EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO, "Variable driver will work with auth variable support!\n"));
      mVariableModuleGlobal->VariableGlobal.AuthSupport = TRUE;
      if (mAuthContextOut.AuthVarEntry != NULL) {
        for (Index = 0; Index < mAuthContextOut.AuthVarEntryCount; Index++) {
          VariableEntry = &mAuthContextOut.AuthVarEntry[Index];
          Status = VarCheckLibVariablePropertySet (
                     VariableEntry->Name,
                     VariableEntry->Guid,
                     &VariableEntry->VariableProperty
                     );
          ASSERT_EFI_ERROR (Status);
        }
      }
    } else if (Status == EFI_UNSUPPORTED) {
      DEBUG ((EFI_D_INFO, "NOTICE - AuthVariableLibInitialize() returns %r!\n", Status));
      DEBUG ((EFI_D_INFO, "Variable driver will continue to work without auth variable support!\n"));
      mVariableModuleGlobal->VariableGlobal.AuthSupport = FALSE;
      Status = EFI_SUCCESS;
    }
  }

AuthVariableLib是用来增加变量的安全性的。

gUefiOvmfPkgTokenSpaceGuid.PcdSecureBootEnable

这个没有特别好介绍的,因为是OVMF独有的,参考意义不大。

gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy

Policy是用来配置Secure Boot的。实际上这样的Policy有多个:

[PcdsFixedAtBuild, PcdsPatchableInModule]
  ## Image verification policy for OptionRom. Only following values are valid:<BR><BR>
  #  NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  #  0x00000000      Always trust the image.<BR>
  #  0x00000001      Never trust the image.<BR>
  #  0x00000002      Allow execution when there is security violation.<BR>
  #  0x00000003      Defer execution when there is security violation.<BR>
  #  0x00000004      Deny execution when there is security violation.<BR>
  #  0x00000005      Query user when there is security violation.<BR>
  # @Prompt Set policy for the image from OptionRom.
  # @ValidRange 0x80000001 | 0x00000000 - 0x00000005
  gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x04|UINT32|0x00000001

  ## Image verification policy for removable media which includes CD-ROM, Floppy, USB and network.
  #  Only following values are valid:<BR><BR>
  #  NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  #  0x00000000      Always trust the image.<BR>
  #  0x00000001      Never trust the image.<BR>
  #  0x00000002      Allow execution when there is security violation.<BR>
  #  0x00000003      Defer execution when there is security violation.<BR>
  #  0x00000004      Deny execution when there is security violation.<BR>
  #  0x00000005      Query user when there is security violation.<BR>
  # @Prompt Set policy for the image from removable media.
  # @ValidRange 0x80000001 | 0x00000000 - 0x00000005
  gEfiSecurityPkgTokenSpaceGuid.PcdRemovableMediaImageVerificationPolicy|0x04|UINT32|0x00000002

  ## Image verification policy for fixed media which includes hard disk.
  #  Only following values are valid:<BR><BR>
  #  NOTE: Do NOT use 0x5 and 0x2 since it violates the UEFI specification and has been removed.<BR>
  #  0x00000000      Always trust the image.<BR>
  #  0x00000001      Never trust the image.<BR>
  #  0x00000002      Allow execution when there is security violation.<BR>
  #  0x00000003      Defer execution when there is security violation.<BR>
  #  0x00000004      Deny execution when there is security violation.<BR>
  #  0x00000005      Query user when there is security violation.<BR>
  # @Prompt Set policy for the image from fixed media.
  # @ValidRange 0x80000001 | 0x00000000 - 0x00000005 
  gEfiSecurityPkgTokenSpaceGuid.PcdFixedMediaImageVerificationPolicy|0x04|UINT32|0x00000003

它会在后续模块的具体实现中使用的,用来定义如何处理各类不同的二进制文件。可以从下面的代码中看出来:

  //
  // Check the image type and get policy setting.
  //
  switch (GetImageType (File)) {

  case IMAGE_FROM_FV:
    Policy = ALWAYS_EXECUTE;
    break;

  case IMAGE_FROM_OPTION_ROM:
    Policy = PcdGet32 (PcdOptionRomImageVerificationPolicy);
    break;

  case IMAGE_FROM_REMOVABLE_MEDIA:
    Policy = PcdGet32 (PcdRemovableMediaImageVerificationPolicy);
    break;

  case IMAGE_FROM_FIXED_MEDIA:
    Policy = PcdGet32 (PcdFixedMediaImageVerificationPolicy);
    break;

  default:
    Policy = DENY_EXECUTE_ON_SECURITY_VIOLATION;
    break;
  }

可以看到对于BIOS本身的二进制FV都是默认执行的;而对于其他的比如OPROM,USB中的Grub等,都需要根据不同的配置来确定如何处理。

SecurityStubDxe.inf

这个是实现Secure Boot的主体驱动,但是其实它也是依赖的一个库的:

!if $(SECURE_BOOT_ENABLE) == TRUE
  MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf {
    <LibraryClasses>
      NULL|SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.inf
	}
!else
  MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf
!endif

所以这里的DxeImageVerificationLib.inf才是重点。后面还会进一步介绍。

SecureBootConfigDxe.inf

这个驱动主要用来提供Setup界面,可以对Secure Boot进行一些必要的配置,正如我们在前面的惠普笔记本的Setup下看到的那样。

下面是vUDK2017这份代码下的Setup界面:

可以看到这里Secure Boot是关闭的。

主体实现

讲到主体实现,就需要回到前面介绍的EFI_SECURITY_ARCH_PROTOCOL和EFI_SECURITY2_ARCH_PROTOCOL的实现。

这里以EFI_SECURITY2_ARCH_PROTOCOL为例,下面是其成员的实现:

EFI_STATUS
EFIAPI
Security2StubAuthenticate (
  IN CONST EFI_SECURITY2_ARCH_PROTOCOL *This,
  IN CONST EFI_DEVICE_PATH_PROTOCOL    *File,
  IN VOID                              *FileBuffer,
  IN UINTN                             FileSize,
  IN BOOLEAN                           BootPolicy
  )
{
  EFI_STATUS                           Status;

  if (FileBuffer != NULL) {
    Status = Defer3rdPartyImageLoad (File, BootPolicy);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  return ExecuteSecurity2Handlers (EFI_AUTH_OPERATION_VERIFY_IMAGE | 
                                   EFI_AUTH_OPERATION_DEFER_IMAGE_LOAD | 
                                   EFI_AUTH_OPERATION_MEASURE_IMAGE |
                                   EFI_AUTH_OPERATION_CONNECT_POLICY, 
                                   0, 
                                   File,
                                   FileBuffer, 
                                   FileSize, 
                                   BootPolicy
                                   );
}

这个实现就是在前面提到的SecurityStubDxe.inf。

ExecuteSecurity2Handlers()的实现中,最重要的是如下的部分:

        Status = mSecurity2Table[Index].Security2Handler (
                   AuthenticationStatus,
                   File,
                   FileBuffer,
                   FileSize,
                   BootPolicy
                   );

这里的mSecurity2Table是一个数组,对应的处理函数就来自DxeImageVerificationLib的注册:

  return RegisterSecurity2Handler (
          DxeImageVerificationHandler,
          EFI_AUTH_OPERATION_VERIFY_IMAGE | EFI_AUTH_OPERATION_IMAGE_REQUIRED
          );

到这里我看可以看到真正做验签的函数就是:

/**
  Provide verification service for signed images, which include both signature validation
  and platform policy control. For signature types, both UEFI WIN_CERTIFICATE_UEFI_GUID and
  MSFT Authenticode type signatures are supported.

  In this implementation, only verify external executables when in USER MODE.
  Executables from FV is bypass, so pass in AuthenticationStatus is ignored.

  The image verification policy is:
    If the image is signed,
      At least one valid signature or at least one hash value of the image must match a record
      in the security database "db", and no valid signature nor any hash value of the image may
      be reflected in the security database "dbx".
    Otherwise, the image is not signed,
      The SHA256 hash value of the image must match a record in the security database "db", and
      not be reflected in the security data base "dbx".

  Caution: This function may receive untrusted input.
  PE/COFF image is external input, so this function will validate its data structure
  within this image buffer before use.

  @param[in]    AuthenticationStatus
                           This is the authentication status returned from the security
                           measurement services for the input file.
  @param[in]    File       This is a pointer to the device path of the file that is
                           being dispatched. This will optionally be used for logging.
  @param[in]    FileBuffer File buffer matches the input file device path.
  @param[in]    FileSize   Size of File buffer matches the input file device path.
  @param[in]    BootPolicy A boot policy that was used to call LoadImage() UEFI service.

  @retval EFI_SUCCESS            The file specified by DevicePath and non-NULL
                                 FileBuffer did authenticate, and the platform policy dictates
                                 that the DXE Foundation may use the file.
  @retval EFI_SUCCESS            The device path specified by NULL device path DevicePath
                                 and non-NULL FileBuffer did authenticate, and the platform
                                 policy dictates that the DXE Foundation may execute the image in
                                 FileBuffer.
  @retval EFI_OUT_RESOURCE       Fail to allocate memory.
  @retval EFI_SECURITY_VIOLATION The file specified by File did not authenticate, and
                                 the platform policy dictates that File should be placed
                                 in the untrusted state. The image has been added to the file
                                 execution table.
  @retval EFI_ACCESS_DENIED      The file specified by File and FileBuffer did not
                                 authenticate, and the platform policy dictates that the DXE
                                 Foundation many not use File.

**/
EFI_STATUS
EFIAPI
DxeImageVerificationHandler (
  IN  UINT32                           AuthenticationStatus,
  IN  CONST EFI_DEVICE_PATH_PROTOCOL   *File,
  IN  VOID                             *FileBuffer,
  IN  UINTN                            FileSize,
  IN  BOOLEAN                          BootPolicy
  )

至于该函数的实现,就可以直接查看代码来了解,这里面涉及一些HASH算法,所以对于这方面也需要有一定的基础,否则可能代码看上去比较困难。

以上,就是对Secure Boot的介绍。

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/105184272
今日推荐