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的介紹。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章