已验证执行

背景

Fuchsia 从零开始设计,旨在支持和扩展强大的启动时验证模型。在大多数具有启动时验证的系统中,验证仅涵盖不超过特定权限级别的代码,并且在某些情况下必须执行未经验证的代码。Fuchsia 将验证纳入了系统的运行时,因此名称为“验证执行”(缩写为 VX 或 FVX)。FVX 的目标只是确保所有可执行代码都是可信的。

除非另有说明,否则本文档使用可执行代码来指代直接在硬件上执行的机器代码。可执行代码不引用沙盒化、解释或以其他方式受限制机制约束的代码。

范围

本文档介绍了针对最终用户 build 类型通过验证执行的一般原则和阶段(除非另有说明)。特定于硬件或产品的实现详情和政策决策不在讨论范围之内。本文档重点介绍了核心验证逻辑,并省略了几个深度防御措施。

安全模型

VX 考虑两种安全模型:运行中的软件模型和启动时验证模型。

+------------------------------------------------------------------------------+
|                                                                              |
|  +---------------------------------------------+--------------------------+  |
|  |                                             |                          |  |
|  | +------------------+ +--------------------+ | +----------------------+ |  |
|  | | Recovery from    | | Recovery from full | | |  Mitigation against  | |  |
|  | | physical attacks | | attacker control   | | |   vulnerabilities    | |  |
|  | +------------------+ +--------------------+ | +----------------------+ |  |
|  |            Verified boot model              | Running software model | |  |
|  +---------------------------------------------+--------------------------+  |
|                                                                              |
|                            Verified execution                                |
+------------------------------------------------------------------------------+

启动时验证安全模型

启动时验证安全模型包含两个方面:从物理攻击中恢复,以及从攻击者完全控制中恢复。人身攻击在很大程度上不在本概述的讨论范围之内,但只要说具有物理接触的攻击者可以完全比具有完全控制权的攻击者完成得简单一些,这一点就足够了。

完全攻击者控制 (FAC) 是指攻击者能够运行他们选择作为“监督程序”或“内核”模式的代码(此处不考虑是否存在更强大的执行模式)。此安全模型中的防御程序的目标是通过消除不可信状态(代码和数据)进行恢复,在这些状态中,攻击者可以在重新启动后保留控制权。

Fuchsia 对启动时验证安全模型的响应是,让计算机只需重新启动即可从任何入侵行为中恢复。在启动时,Fuchsia 会以递归方式验证执行,以保证

  1. 根据“锚”(例如 ROM 中的代码),所有已执行代码都是可信的;
  2. 根据同一锚点,所有用作代码输入的数据都是可信的。

此保证可让系统完全移除不可信状态,即使存在任意有害漏洞也是如此。

防回滚

启动时验证模型假定攻击者可以将大容量存储内容替换为他们选择的内容。在这种模型下,如果没有基于硬件的防回滚机制,任何可在重新启动后被利用的安全漏洞都无法得到修补。如果没有此类机制,攻击者可能会刷写存在漏洞的较旧版本操作系统,而这些版本会通过启动时验证检查,然后重放用于重新获取 FAC 所需的漏洞。基于硬件的防回滚功能可以在修补漏洞后拒绝启动旧版本的 Fuchsia,从而完全避免这种情况。

“运行软件”模型

在此模型中,软件运行和攻击者尝试利用软件中的漏洞来控制正在运行的软件,通常是通过为软件创建恶意输入来实现控制。在此安全模型中,防御者的目标是通过强化代码来防范恶意输入,从而解决或缓解可能的漏洞。

Fuchsia 对运行的软件模型的响应是仅允许可信代码执行。(我们稍后会详细定义什么是“值得信赖”。)

原则

下面列出了实现 Fuchsia 验证执行的通用原则(并非详尽无遗):

  • 应根据有意向的政策来验证代码和关联的只读数据。每种代码、产品或 build 类型的政策可能并不相同,但所有代码都应遵守一项政策。一个简单的政策示例是“除非获得可信方的数字签名,否则代码无法执行”。

    • 没有默认或后备政策。
    • 政策不允许使用一次性、旁加载的代码(不能称“雪花”)。
    • 政策很少支持即时编译 (JIT)。
  • 政策验证和加密方案的输入应尽可能简单。

    • 政策验证不应要求解析不受信任的复杂数据。
  • 验证执行可在各种硬件上实现,但强烈建议使用硬件不可变信任锚和防篡改存储空间(例如,用于存储只能由固件(而非内核)修改的回滚索引)。

验证的阶段

使用“哈希和签名”范式进行验证后,代码和数据被视为可信:数据以“消息+签名”对的形式加载,并使用可信的公钥进行验证。消息包含较大的代码或数据 blob 的加密哈希,这些哈希在被视为可信之前会先经过哈希处理和验证。

第 0 阶段:硬件到第一个引导加载程序

第 0 阶段包括硬件到软件的转换。由于验证执行无法防范硬件攻击,因此在本文档中,我们假定硬件可信。在此阶段,不可变代码会根据不可变信任锚(例如一次性可编程 [OTP] 内存中的公钥)验证第一个引导加载程序。详细信息因硬件而异。

第一阶段:第一个引导加载程序到主引导加载程序

第一个引导加载程序经过验证后,可信将会验证并执行启动系统所需的其他软件。它可能是一系列软件映像,其中每个映像会验证并执行下一个映像,也可能是树状流。此阶段也特定于硬件。

第二阶段:从主引导加载程序到预授权代码

主引导加载程序负责验证预授权代码,即产品授权中心明确批准可作为该产品核心部分运行的所有代码。预授权代码可以直接在硬件上运行(即不使用沙盒),并且包含 Fuchsia 系统的所有必需元素(例如 Zircon 内核、软件包管理系统、驱动程序)。AVB

从主引导加载程序中进行委托

引导加载程序验证 ZBI 后,系统会在两个关键点上委派验证责任。

  1. 经过验证的 ZBI 会验证包裹管理系统。具体而言,ZBI 会从引导加载程序接收软件包管理系统的精确描述,并强制执行适用于该系统的所有政策。
  2. 通过验证后,软件包管理系统将负责验证不属于 ZBI 或软件包管理系统的所有剩余预授权代码。

直接验证与间接验证

直接验证通过精确描述允许运行的软件(例如,使用软件包的加密哈希)来准确指定要信任的内容。间接验证指定信任谁,并将内容交给委派的机构。软件包管理系统成为 VX 政策执行程序后,可能会使用间接验证来支持即时交付软件,而无需进行完整的系统更新和重新启动。为了维护信任链,间接验证流程(ZBI,软件包管理系统)中涉及的组件和配置始终必须直接进行验证。

请注意,直接验证的说明可能任意复杂。如果一个签名附加到一个包含更多哈希的文件的哈希值,那么所有经过哈希处理的内容仍会直接进行验证。

阶段三:非预授权代码

如果代码及其签名授权均未得到产品所有者的授权,则该代码不会过多地验证,因为它受到静态政策的约束。某些专用设备可能根本无法运行未经预授权的代码。软件包管理系统和组件框架的作用是强制执行静态政策。

强烈意向与弱意向

具有强烈意向的非预授权代码是指充当管理角色的用户已明确授权在特定环境中运行的软件包中的代码。此类未经授权的代码可在与预授权代码隔离的环境中直接在计算机上运行。意图弱的未预授权代码是指意外加载或由不担任管理员角色的用户授权的代码(例如,由网页加载的 JavaScript)。意图弱的未预授权代码必须沙盒化、解释,或以其他方式受严格的限制机制约束。

实现

在强制执行经过验证的执行政策时,以下 Fuchsia 系统不可或缺。

主引导加载程序

主引导加载程序实现依赖于 Android 启动时验证进行验证和内核回滚保护。

BlobFS

BlobFS 是一种内容地址加密文件系统,专为支持验证执行而构建。BlobFS 是可执行代码和关联只读数据(预内核代码、内核及其 bootfs 除外)ramdisk 的唯一存储系统,所有这些 ramdisk 都存储在 ZBI 中。BlobFS 中的每个 blob 都由哈希(Merkle 根)唯一表示和访问,并且 Merkle 树结构允许随机 blob 访问。从计算上来说,攻击者如果不更改哈希值就更改 blob,这是不可行的。

在第二阶段加载 BlobFS 后,验证预授权代码的方法只是检查已加载的每个 blob 哈希的签名,并确保 blob 内容与哈希匹配。BlobFS 会执行内容验证,签名检查将委托给软件包管理系统。

包裹管理系统

软件包管理系统(也称为软件交付 (SWD) 堆栈)在 BlobFS 上增加了一层,使得处理 blob 对程序员来说更加友好。SWD 堆栈会将描述软件包的 Merkle 根(具体而言,即软件包 meta.far 的 Merkle 根)转换为人类可读的软件包名称。如果任何软件包内容发生变化,meta.far 的 Merkle 根目录也会发生变化。为简洁起见,本文档将使用“软件包哈希”来引用软件包的 meta.far Merkle 根。

SWD 堆栈管理一个名为系统映像的特殊软件包,其中包含所有基础软件包的哈希列表。在第二阶段加载软件包管理系统时,ZBI 会验证系统映像软件包,因此所有基础软件包都会被直接验证;也就是说,启动后不需要进行签名检查。对于某些专门打造的设备,可以配置 SWD 堆栈,以便仅将基础软件包加载到具有执行权限的内存中,这意味着设备将仅执行直接验证的代码。

对于间接验证代码(“Universe 软件包”),可信公钥和提供这些公钥的委派机构的其他元数据包含在启动时验证签名涵盖的位置(例如 ZBI 或基础软件包)。从此类授权方下载的任何软件包都必须在每次加载软件包时通过签名和内容哈希进行验证(而不是在下载时检查签名一次)。“防回滚版本”元数据可以包含在直接验证的数据中,以确保直接和间接验证的软件包均受硬件防回滚保护的保护。

SWD 堆栈还负责下载系统更新。它有许多控件,防止正在进行的更新中的任何 blob 在设备重新启动(并因此通过验证)更新之前就可供正在运行的系统使用。

组件框架

软件包是软件分发的单元,而组件是 Fuchsia 的软件执行标准单元。这种区别的一个结果是,大多数 Fuchsia 组件不会直接与 SWD 堆栈交互。相反,SWD 软件包解析器的主要客户是组件框架,该框架包含许多委托 SWD 堆栈加载代码和数据的组件解析器。某些组件解析器仅限于基础软件包,而其他组件解析器可能允许访问 Universe 软件包。与其他 Fuchsia 功能一样,这些解析器在组件清单中进行路由,这样可提供额外的控制和可审核性。例如,间接验证的代码可以仅限于特定的环境