赞
踩
Secure Boot,顾名思义就是用来保证启动安全的一套措施。
Secure Boot是一个比较普通的说法,使用的场景也很多,所以这里要特别说明一下,这里指的是UEFI BIOS下的,用来启动诸如Windows、Mac OS之类系统的“Secure Boot”。
Secure Boot最早在《UEFI Spec》2.3.1版本中提出,关于它的具体说明,可以参考下面的网站:
Frequently Asked Questions about Secure Boot
为什么要使用Secure Boot?
首先BIOS执行的过程中可能需要运行一些第三方的程序。比如有一张显卡,其上包含OPROM(就是BIOS下可运行的初始化程序),BIOS为了初始化这张显卡,就会从该显卡处获取到OPROM然后执行,这是一个例子,但是对于这种情况还好,即使不执行显卡的初始化,影响也不大。但是还存在另外的一个东西,是大部分系统启动所必须的,那就是GRUB。通常支持UEFI的系统在安装过程中都会将一个GRUB文件到磁盘的FAT32分区中,然后UEFI BIOS启动时会执行这个文件,并通过这个文件找到系统内核并启动操作系统。
这个GRUB也是一个第三方的程序,关于它可以参考下面的文章:
另外,在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,实际上在保护Windows启动过程 - Microsoft 365 Security | Microsoft Docs中也有说明:
- 使用具有认证的启动加载程序的操作系统。 由于所有已认证的 Windows10 电脑必须信任 Microsoft 的证书,Microsoft 提供了一种服务来分析和签署任何非 Microsoft 的引导程序,以便所有认证的 Windows10 电脑能够获得信任。 事实上,已经有了能够加载 Linux 的开源启动加载程序。 若要开始获取证书的过程,请转到Sign in to your account。
- 将 UEFI 配置为信任自定义的启动加载程序。 所有经认证的 Windows10 电脑都允许你通过将签名添加到 UEFI 数据库来信任未验证的加载程序,从而允许你运行任何操作系统,包括 homemade 操作系统。
- 关闭安全启动。 所有经认证的 Windows10 Pc 允许您关闭安全启动,以便您可以运行任何软件。 但是,这不会帮助保护你免受 bootkit 的攻击。
默认UEFI支持微软的key,即通过微软签名的第三方程序UEFI会认为是安全的。同时我们也可以自己给第三方程序签名,然后通过使用自己的key来对第三方程序进行验签。
最后的最后,再说明下对于启动Linux的GRUB,事实上现在已经有可以支持安全启动和Linux版本的GRUB,称为shim,可以在mjg59 | Secure Boot bootloader for distributions available now中看到具体的说明。
对于第三方的应用如何进行验签,可以从UEFI Shell下执行UEFI应用开始。
对应的代码如下(位于Shell.c中,参考代码vUDK2017: https://github.com/tianocore/edk2.git Tag 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的组件。
该库非常简单,只提供了一个接口:
- /**
- 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的相关修改必须有实际存在的人进行操作,比如说会弹出一个框,需要接键盘来选择确认,当然这个会根据不同的平台来实现,有些可能根本就不需要特别实现。
它也提供了一个接口:
- /**
- 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使用的,应该并不是必需的。
它是对变量的扩展,在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是用来增加变量的安全性的。
这个没有特别好介绍的,因为是OVMF独有的,参考意义不大。
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等,都需要根据不同的配置来确定如何处理。
这个是实现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才是重点。后面还会进一步介绍。
这个驱动主要用来提供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的介绍。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。