UEFI 验证选项 ROM 指南

版本 1.3

本文档可帮助 OEM 和 ODM 验证其固件是否检查属于安全启动信任链一部分的选项 ROM 的签名。

本指南假设读者具备 UEFI 的基础知识,并对安全启动(UEFI 规范第 1、2、13、20 和 27 章)和 PKI 安全模型有基本的了解。

简介

选项 ROM(Option ROM,简写为 OpROM)是在平台初始化期间由电脑 BIOS 运行的固件。 它们通常存储在插件卡上,不过也可以驻留在系统板上。

通常需要选项 ROM 的设备包括视频卡、网络适配器和 RAID 模块的存储驱动程序。 这些选项 ROM 通常还为电脑提供固件驱动程序。

它们包括各种类型的固件驱动程序,例如传统的 PC-AT、开放式固件和 EFI 选项 ROM。 固件驱动程序的示例包括视频卡上的视频 BIOS、以太网适配器的 PXE 启动驱动程序,以及 RAID 控制器上的存储驱动程序。 这些设备通常具有提供固件驱动程序的选项 ROM。

统一可扩展固件接口 (UEFI) 支持传统模式选项 ROM。

根据最新的 UEFI 规范(目前为 2.3.1 勘误表 C – 第 2.5.1.2 部分),ISA(传统)选项 ROM 不是 UEFI 规范的一部分。 在本文的讨论范围内,只考虑基于 PCI 的与 UEFI 兼容的选项 ROM。

无法将设备的固件嵌入到电脑固件中时,可以使用选项 ROM。 选项 ROM 携带了驱动程序时,IHV 可以利用该驱动程序,并将其与设备放在同一位置。

本文档讨论为何需要验证选项 ROM,并介绍一些验证方法。

同时支持 UEFI BIOS 和传统 BIOS

许多制造商为多种类型的电脑制造包含选项 ROM 和固件的设备。 常见的组合包括:

  • 仅传统 ROM
  • UEFI 本机 OpROM
  • 传统 ROM + UEFI EBC OpROM
  • 传统 ROM + UEFI x64 OpROM
  • 传统 ROM + UEFI x64 + UEFI IA32
  • 传统 ROM + UEFI x64 + UEFI IA32 + UEFI EBC OpROM

UEFI BIOS 在启用兼容性支持模块 (CSM) 后可以加载和执行传统固件驱动程序。 请注意,在启用安全启动后,将禁止执行兼容性支持模块和传统 ROM,因为传统固件驱动程序不支持身份验证。如果 BIOS 配置中的选项 ROM 格式设置为传统 ROM,则始终会在设备上使用传统 ROM。

如果选项 ROM 格式设置为“UEFI 兼容”,则会使用较新的 EFI ROM(如果有)或旧版 ROM(如果新版不存在)

对于许多新的固件级安全功能而言,以及若要启用 UEFI 启动序列,UEFI 驱动程序都是必要的。 例如,当系统在 UEFI 模式下启动且已启用安全启动时,无法从附加到不兼容 UEFI 的存储控制器的光盘安装 Windows。

1. UEFI 和选项 ROM

diagram of uefi driver considerations

图 2:UEFI 驱动程序安全注意事项,来源:UEFI 2.3.1 勘误表 C

以下文本源自 UEFI 2.3.1 勘误表 C,但后来已根据合作伙伴的意见做了修改

由于 UEFI 用户配置文件详细说明了许多安全相关的特权,因此必须确保用户身份管理器和用户凭据提供程序及其执行环境受信任。

这包括:

  • 保护用于存储这些驱动程序的存储区域。
  • 保护这些驱动程序的选择方式。
  • 保护这些驱动程序的执行环境,防止在其中执行未经验证的驱动程序。
  • 当这些驱动程序使用的数据结构仍在使用中时,不应被未经授权的驱动程序损坏。

用户身份管理器、用户凭据驱动程序和板载驱动程序等组件也许位于安全位置,例如,位于平台策略信任的写保护闪存驱动器中。

其他某些驱动程序可能驻留在不受保护的存储位置(例如选项 ROM 或硬盘驱动器分区中),可能很容易被替换。 这些驱动程序必须经过验证。

例如,默认平台策略必须能够成功验证“驱动程序####”加载选项中列出的驱动程序,要么必须在处理这些驱动程序之前识别用户。 否则,驱动程序的执行应该推迟。 如果通过后续调用 identify () 或通过动态身份验证更改了用户配置文件,则可能不会再次处理“驱动程序####”选项。

用户配置文件数据库是根据它是否可受保护,使用不同的 UEFI 信号事件关闭的。

只会对启动路径中的设备执行 UEFI 驱动程序和 UEFI 选项 ROM。

PCI 规范允许同一设备上存在多个选项 ROM 映像。 这些选项 ROM 可以是传统的 x86 和 UEFI 选项 ROM。 UEFI 固件将设置用于选择选项 ROM 的平台策略。 这样,可选适配器的 ROM 可作为其自身的控制设备执行。

固件在 BDS 和 DXE 阶段验证签名。 事件的顺序如下:

  1. 初始化 PCI 和衍生的总线
  2. 探测选项 ROM 的 PCI 设备
  3. 在内存中映射已找到的选项 ROM
  4. DXE 阶段加载 ROM 中的任何 UEFI 驱动程序

UEFI 选项 ROM 可以位于内存中的任何位置。 默认设置是让卡上的 ROM 管理设备。 UEFI 允许平台根据哪个选项 ROM 控制哪个设备,使用 EFI_PLATFORM_DRIVER_OVERRIDE 来控制策略。 UEFI 支持使用选项 ROM 注册配置接口。

在启用了安全启动的电脑上,如果选项 ROM 驱动程序未签名或未经验证,它们会造成安全威胁。 对选项 ROM 进行签名验证是一项 WHCK 要求。 在维护选项 ROM 时也是如此,目的是确保在安装更新之前先对其进行验证。

Windows 硬件兼容性计划规范和策略版本 1809 中:

  1. 已签名固件代码完整性检查。 可将 OEM 安装的,且为只读的或者受上面定义的安全固件更新过程保护的固件视为受保护。 系统应验证所有不受保护的固件组件、UEFI 驱动程序和 UEFI 应用程序是否至少已使用 RSA-2048 和 SHA-256(禁止使用 MD5 和 SHA-1)进行签名,并验证未按照这些要求签名的 UEFI 应用程序和驱动程序是否无法运行(这是可接受的签名算法的默认策略)。 如果在已授权的数据库中找不到映像签名,或者在受禁止的数据库中找到了映像签名,则不得启动该映像,而应该将有关该映像的信息放到映像执行信息表中。

2. 问题陈述

某些已启用安全启动的 UEFI BIOS 版本(包括 Tiano Core)默认不会验证 UEFI 选项 ROM,因为在安全启动开发期间,已签名的 UEFI 选项 ROM 不可用。 这会在 UEFI 安全启动中暴露一个攻击面/漏洞。

2.1. 漏洞

截至 2013 年 8 月,此漏洞仍然存在于 EDK II 和 UDK2010 中。 源代码维护者已知道此问题并提交了 bug。 任何派生自 EDK II 和 UDK2010 的固件都应验证选项 ROM 验证的管理方式。 选项 ROM 验证行为由 EDK II SecurityPkg 包中的 PCD 值 PcdOptionRomImageVerificationPolicy 控制。

TianoCore 漏洞的源代码是 SecurityPkg\SecurityPkg.dec 文件:

## Pcd for OptionRom.
  #  Image verification policy settings:
  #  ALWAYS_EXECUTE                         0x00000000
  #  NEVER_EXECUTE                          0x00000001
  #  ALLOW_EXECUTE_ON_SECURITY_VIOLATION    0x00000002
  #  DEFER_EXECUTE_ON_SECURITY_VIOLATION    0x00000003
  #  DENY_EXECUTE_ON_SECURITY_VIOLATION     0x00000004
  #  QUERY_USER_ON_SECURITY_VIOLATION       0x00000005
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00|UINT32|0x00000001

默认值 (0x00) 是 ALWAYS_EXECUTE,它不会正确验证选项 ROM 中用于附加外围设备的已签名驱动程序。 对于任何实现 UEFI 安全启动功能的系统而言,这不是一个理想值。

建议的值(最佳安全性):DENY_EXECUTE_ON_SECURITY_VIOLATION (0x04)

建议的值(最佳灵活性):QUERY_USER_ON_SECURITY_VIOLATION (0x05)

在 EDK II 和 UDK2010 中,正确的编程实践使用一个重写机制来修改平台固件的 PCD 值。 因此,不应在 SecurityPkg\SecurityPkg.dec 中更改 PcdOptionRomImageVerificationPolicy 的值。 应在平台的 DSC 文件中设置重写值。 下面显示了一个使用 Nt32Pkg\Nt32Pkg.dsc 的示例:

[PcdsFixedAtBuild]
gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x04

PCD 重写应放在 DSC 文件的 [PcdsFixedAtBuild] 节下。 确切的参数重写机制根据 BIOS 供应商工具的不同而异。

注意

此漏洞可能存在于独立 BIOS 供应商 UEFI 安全启动 BIOS 的早期实现中。 请与 BIOS 供应商联系,确定你的版本是否受影响。

3. 哪些设备会受到影响?

实现安全启动并包含未签名 UEFI 选项 ROM 驱动程序的 UEFI 电脑。 此外,使现有卡正常运行的兼容性固件可能存在不验证选项 ROM 的安全漏洞。

笔记本电脑、上网本、超极本和平板电脑:大多数不受影响。 选项 ROM 通常安装在背板总线上,例如 PCI/e、ISA 及其衍生产品(ExpressCard、miniPCI、CardBus、PCCard、LPC、ThunderBolt 等)。 如果笔记本电脑未暴露这些漏洞,则其受攻击面会大大减小。 此外,板载笔记本电脑组件的 UEFI 驱动程序有可能已集成到核心 BIOS 固件卷中,而不是位于单独的选项 ROM 上。 因此,大多数笔记本电脑都没有风险。 此外,当禁用传统选项 ROM 时,UEFI 似乎仅支持基于 PCI 的选项 ROM。

但是,如果你的台式机、主板或服务器搭载了 UEFI BIOS 并实现安全启动,则你可能会受影响。 在服务器的专用 RAID 控制器、用于 SATA、FC 等的附加存储控制器或以太网 PCIe 网卡上,可能有选项 ROM。 服务器上往往有一些支持各种功能的附加控制器,因此,这种情况尤其适用于服务器领域。

这可能会影响 32 位和 64 位电脑,包括第 2 类和第 3 类。

如果安全启动平台支持未永久附加到平台的设备中的选项 ROM,并且支持对这些选项 ROM 进行身份验证的功能,则它必须支持“网络协议 — UDP 和 MTFTP”中所述的选项 ROM 验证方法,以及 UEFI 规范 2.3.1 勘误表 C 第 7.2 部分中所述的已经过身份验证的 EFI 变量。

4. 如何测试?

如果你正在开发基于 Tiano Core 的固件,请检查第 2.1 部分中所述的漏洞。 如果使用其他 IBV 的固件,请与他们核实。 或者,你可以如下所述自行测试。

需要以下各项:

  • 带有 UEFI 固件的受测电脑
  • 受测电脑上带有选项 ROM 的 PCI 设备(例如视频卡)
  • 确保已启用安全启动

测试步骤:

  1. 将带有 UEFI 选项 ROM 的 UEFI 附加 PCI 卡插入受测电脑。

    如果使用 PCI 视频卡进行测试,请连接外部监视器。

  2. 使用以下设置启用安全启动:

    • PK:你的 PK 或自签名测试 PK
    • KEK:MS KEK、PK 签名的 Fabrikam 测试 KEK 或其他 KEK
    • DB:NULL。 (必须为 NULL。)
    • DBX:NULL。
    • SecureBoot:UEFI 变量应设置为 true
  3. 重启电脑

  4. 预期的结果如下:

    • 如果 UEFI 固件正确实现,则 UEFI 选项 ROM 驱动程序将不会加载,因为存在选项 ROM 会导致固件在“Db”中检查某个证书。 由于“Db”为 NULL,因此 UEFI 驱动程序无法加载。 例如,如果你正在使用视频卡进行测试,将会发现显示器上未显示任何内容。
    • 如果固件未正确实现,UEFI 驱动程序将从选项 ROM 加载,因为固件不会检查“Db”中的签名。 例如,如果你正在使用视频卡进行测试,将会发现连接到选项 ROM 卡的监视器显示了内容。

    ![note] UEFI 选项 ROM 驱动程序是否已签名并不重要;当 DB 为 null 且已启用 SB(注册了 PK 和 KEK)时,选项 ROM 不会加载。

请参考 WHCK 中提供的用于生成 PK 和 KEK 的示例脚本。 附录 B 提供了示例脚本和更多详细信息。

还可以参考附录 A,了解执行上述测试的另一种方法。 此方法不要求将 DB 设置为 Null,但需要 IHV 提供一个未签名的 UEFI 选项 ROM 驱动程序。

5. 如何修复

如果上述测试失败,请与 IBV 协作,以获取并配置所需的版本来验证选项 ROM。 确保固件能够通过测试。 对于已交付的电脑,需要进行安全固件更新。 请参阅 NIST 出版物 800-147 和/或 Windows 8.1 安全启动密钥创建和管理指南

可以测试电脑,并利用 Windows HCK 作为测试工具(不是认证工具)来测试安全固件更新。

5.1. 为驱动程序签名

如果你发现 UEFI 选项 ROM 上可能存在未签名的驱动程序,请阅读下文以了解如何修复。

单独为每个选项 ROM 驱动程序签名。 这会破坏 PCI 选项 ROM 的格式。 只需在创建组合的选项 ROM 之前对 UEFI 驱动程序进行签名。

在将 UEFI 驱动程序插入 OpROM 之前,为 UEFI 映像签名,并在 UEFI Shell 中将安全启动设置为 ON 和 OFF(加载/卸载驱动程序文件)的情况下测试该映像。 然后将已签名的驱动程序放入组合的选项 ROM 中。

可将 IHV 转到 Microsoft SysDev 中心,以通过 SysDev 中心提供的某个服务将其 UEFI 选项 ROM 签名。

5.2. 更新验证

运行上述测试,以验证是否没有漏洞。 使用 HCK 测试来确保不存在功能倒退。

6. 资源

附录 A:使用未签名选项 ROM 驱动程序进行测试的替代方法

此方法依赖于从 IHV 获取工具来确保为 UEFI 选项 ROM 驱动程序签名。

需要以下各项:

  • 带有 UEFI 固件的受测电脑
  • 带有未签名选项 ROM 驱动程序的 PCI 设备(例如视频卡),已附加到受测电脑
  • 确保已启用安全启动
  • 选项 IHV 工具,用于在无法明确知道选项 ROM 驱动程序是否已签名时,检测选项 ROM 驱动程序上的签名

如果正确实现了固件,并且选项 ROM 未签名,则卡应该通不过固件的检查,并且不会在卡上加载驱动程序。 电脑应报告错误代码,例如 EFI_IMAGE_EXECUTION_AUTH_SIG_FOUND。 如果使用的是视频卡,你可能会发现电脑显示黑屏,因为选项 ROM 驱动程序并未加载。

如果错误地实现固件,则此项测试将会正常进行。

附录 B:用于通过 NULL db 启用安全启动的脚本

可以使用当前的安全启动变量集(PK 和 KEK),或者生成测试变量来完成此项测试。

下面是用于生成测试 PK、KEK 并将 Db 设置为 NULL 的步骤。 请确保未启用安全启动;否则这些步骤需要提供已签名的 UEFI bin 文件。

注意

我们以相反的顺序设置安全启动变量 – Db、KEK 和 PK,以避免为 UEFI bin 文件签名。

在执行此步骤之前,电脑应处于设置模式。

  1. 创建 KEK 和 PK 证书

    此步骤需要 Windows SDK 中提供的 makecert.exe 工具。

    MakeCert.exe -cy authority -len 2048 -m 60 -a sha256  -pe -ss my -n "CN=DO NOT SHIP - Fabrikam Test KEK CA" Fabrikam_Test_KEK_CA.cer
    MakeCert.exe -cy authority -len 2048 -m 60 -a sha256  -pe -ss my -n "CN=DO NOT SHIP - Fabrikam Test PK" TestPK.cer
    
  2. 用于生成测试 PK 的脚本

    下面提供了示例。

    this scripts demonstrates how to format the Platform key
    NOTE The PK is actually set in the Enable_OEM_SecureBoot.ps1 script
    Import-Module secureboot
    $d = (pwd).Path
    
    ###############################################################################
    Complete the following parameters
    ###############################################################################
    
    $certname = "TestPK"
    TODO change this path to where you have the PK.cer file
    This is where you plugin the certificate generated by the HSM
    $certpath = $d + "\" + $certname + ".cer"
    
    Each signature has an owner SignatureOwner, which is a GUID identifying the agent which inserted the signature in the database.
    Agents might include the operating PC or an OEM-supplied driver or application.
    Agents may examine this field to understand whether they should manage the signature or not.
    TODO replace with OEM SignatureOwner GUID.
    You can use tools like Guidgen.exe tool in SDK or a similar tool to generate a GUID
    $sigowner = "55555555-5555-5555-5555-555555555555"
    
    $var = "PK"
    $efi_guid = "{8BE4DF61-93CA-11d2-AA0D-00E098032B8C}"
    $append = $false
    
    ###############################################################################
    Everything else is calculated
    ###############################################################################
    
    Workaround relative path bug
    TODO substitute OEM with your OEM name
    $siglist =  $certname + "_SigList.bin"
    $serialization = $certname + "_SigList_Serialization_for_" + $var + ".bin"
    $signature = $serialization + ".p7"
    
    $appendstring = "set_"
    $attribute = "0x27"
    $example = "Example_SetVariable_Data-" + $certname + "_" + $appendstring + $var + ".bin"
    
    Format-SecureBootUEFI -Name $var -SignatureOwner $sigowner -ContentFilePath $siglist -FormatWithCert -Certificate $certpath -SignableFilePath $serialization -Time 2011-05-21T13:30:00Z  -AppendWrite:$append
    
    OutputFilePath - Specifies the name of the file created that contains the contents of what is set.
    If this parameter is specified, then the content are not actually set, just stored into this file.
    Please note if -OutputFilePath is provided the PK is not set like in this case. The master script sets it at the end.
    
    Time - you can change the time below as long as it isn't in the future. Nothing wrong with keeping it as is.
    
    Set-SecureBootUEFI -Name $var -Time 2011-05-21T13:30:00Z -ContentFilePath $siglist  -OutputFilePath $example -AppendWrite:$append
    
  3. 生成测试 KEK 或使用自己的 OEM KEK

    为此,你可以利用自己的 OEM KEK,或 WHCK 中的脚本。

    下面提供了示例。

    script to add option OEM KEK
    Import-Module secureboot
    $d = (pwd).Path
    
    ###############################################################################
    Complete the following parameters
    ###############################################################################
    
    $certname = "Fabrikam_Test_KEK_CA"
    TODO change this path to where you have the PK.cer file
    This is where you plugin the certificate generated by the HSM
    $certpath = $d + "\" + $certname + ".cer"
    
    TODO change this path to where you have the OEM_KEK.cer file
    Each signature has an owner SignatureOwner, which is a GUID identifying the agent which inserted the signature in the database.
    Agents might include the operating system or an OEM-supplied driver or application.
    Agents may examine this field to understand whether they should manage the signature or not.
    TODO replace with OEM SignatureOwner GUID.
    You can use tools like Guidgen.exe tool in SDK or a similar tool to generate a GUID
    
    $sigowner = "00000000-0000-0000-0000-000000000000"
    
    $var = "KEK"
    $efi_guid = "{8BE4DF61-93CA-11d2-AA0D-00E098032B8C}"
    $append = $false
    
    ###############################################################################
    Everything else is calculated
    ###############################################################################
    
    $siglist = $certname + "_SigList.bin"
    $serialization = $certname + "_SigList_Serialization_for_" + $var + ".bin"
    $signature = $serialization + ".p7"
    if ($append -eq $false)
    {
    $appendstring = "set_"
    $attribute = "0x27"
    } else
    {
    $appendstring = "append_"
    $attribute = "0x67"
    }
    $example = "Example_SetVariable_Data-" + $certname + "_" + $appendstring + $var + ".bin"
    
    Format-SecureBootUEFI -Name $var -SignatureOwner $sigowner -ContentFilePath $siglist -FormatWithCert -CertificateFilePath $certpath -SignableFilePath $serialization -Time 2011-05-21T13:30:00Z -AppendWrite:$append
    
    -Time You can change the time below as long as it isn't in the future. Nothing wrong with keeping it as is.
    
    Set-SecureBootUEFI -Name $var -Time 2011-05-21T13:30:00Z -ContentFilePath $siglist -OutputFilePath $example -AppendWrite:$append
    
  4. 将 Db 设置为 Null 并设置 KEK 和 PK

    此脚本执行的第一项操作是将 Db 设置为 Null。

    注意

    请记住,如果 Fabrikam Test KEK CA 是唯一存在的 KEK CA(意味着没有 Windows KEK CA),电脑可能会启动进入 Windows RE。

    在执行脚本之前,请运行“Set-ExecutionPolicy Bypass -Force”

    Import-Module secureboot
    try
    {
    Write-Host "Deleting db..."
    Set-SecureBootUEFI -Name db -Time "2011-06-06T13:30:00Z" -Content $null
    }
    catch
    {
    }
    Write-Host "Setting Fabrikam KEK..."
    Set-SecureBootUEFI -Time 2011-05-21T13:30:00Z  -ContentFilePath Fabrikam_Test_KEK_CA_SigList.bin  -Name KEK
    
    Write-Host "Setting self-signed Test PK..."
    Set-SecureBootUEFI -Time 2011-05-21T13:30:00Z -ContentFilePath TestPK_SigList.bin  -Name PK
    
    Write-Host "`n... operation complete.  `nSetupMode should now be 0 and SecureBoot should also be 0. Reboot and verify that Windows is correctly authenticated, and that SecureBoot changes to 1."
    
  5. 插入选项 ROM 卡并测试

    测试应该基于固件的正确性,结果为通过或失败。 例如:

    如果正确实现固件中的选项 ROM,并且你正在使用视频卡进行测试,则附加的监视器应该不会显示任何内容。

    但是,如果使用错误的固件,则视频卡应会在显示器上提供输出。

Windows 安全启动密钥创建和管理指南

安全启动概述

验证 Windows UEFI 固件更新平台功能