背景
Fuchsia 从一开始就设计为支持和扩展强大的经过验证的启动模型。在大多数启用启动时验证的系统中,验证仅涵盖最高级别的权限级别的代码,并且在某些时候必须执行未经验证的代码。Fuchsia 将验证融入了系统运行时,因此得名“已验证执行”(缩写为 VX 或 FVX)。FVX 的目标只是确保所有可执行代码都是可信的。
除非另有说明,否则本文档中使用可执行代码来指代直接在硬件上执行的机器代码。可执行代码不指沙盒化、解释或受其他限制机制约束的代码。
范围
本文档介绍了针对最终用户 buildtype 的经过验证的执行的一般原则和阶段(除非另有说明)。硬件或产品专用的实现详情和政策决定不在讨论范围之内。本文档重点介绍核心验证逻辑,而省略了多层纵深防御。
安全模型
VX 考虑了两种安全模型:正在运行的软件模型和经过验证的启动模型。
+------------------------------------------------------------------------------+
| |
| +---------------------------------------------+--------------------------+ |
| | | | |
| | +------------------+ +--------------------+ | +----------------------+ | |
| | | Recovery from | | Recovery from full | | | Mitigation against | | |
| | | physical attacks | | attacker control | | | vulnerabilities | | |
| | +------------------+ +--------------------+ | +----------------------+ | |
| | Verified boot model | Running software model | | |
| +---------------------------------------------+--------------------------+ |
| |
| Verified execution |
+------------------------------------------------------------------------------+
启动时验证安全模型
启动时验证安全模型包含两个方面:从物理攻击中恢复,以及从攻击者的完全控制中恢复。本概览主要介绍的是软件攻击,但需要指出的是,与拥有完全控制权的攻击者相比,拥有物理访问权限的攻击者能完成的攻击行为要少得多。
完全攻击者控制 (FAC) 是指攻击者能够以“supervisor”或“kernel”模式运行其选择的代码(本文不考虑存在更强大的执行模式)。在此安全模型中,防御者的目标是通过消除攻击者可以在重启后保留控制权的不信任状态(代码和数据)来恢复。
Fuchsia 对经过验证的启动安全模型的回应是,让计算机只需重新启动即可从任何入侵中恢复。在启动时,Fuchsia 经过验证的执行会递归地保证
- 所有已执行代码均根据“锚点”(例如 ROM 中的代码)可信,并且
- 根据同一锚点,所有用作代码输入的数据都是可信的。
有了这一保证,即使存在任意严重漏洞,系统也能完全移除不可信状态。
防回滚
经过验证的启动模型假定攻击者可以将大容量存储内容替换为他们选择的内容。在这种模式下,如果没有基于硬件的防回滚机制,那么任何可在重新启动后被利用的安全漏洞无法进行修补。如果没有这种机制,攻击者可能会刷写较老的易受攻击版本的操作系统,该版本将通过启动时验证检查,然后重放重新获取 FAC 所需的漏洞。基于硬件的防回滚功能在修补漏洞之后拒绝启动旧版 Fuchsia,从而完全避免这种情况。
正在运行的软件模型
在此模型中,软件会运行,攻击者会尝试利用软件中的漏洞来控制正在运行的软件,通常是通过向软件构造恶意输入来实现。在此安全模型中,防御者的目标是通过对抗恶意输入来强化代码,从而解决或缓解可能存在的漏洞。
Fuchsia 对运行的软件模型的响应是将执行限制为可信代码。(我们稍后会更详细地介绍什么是“可信”。)
原则
以下列出了指导 Fuchsia 验证执行实现的一般原则(并非详尽无遗):
应根据有意政策对代码和关联的只读数据进行验证。政策可能并非每种代码、产品或 build 类型的政策都相同,但所有代码都应绑定到政策。以下是一个简单的政策示例:“除非代码已由受信任方进行数字签名,否则无法执行。”
- 没有默认或后备政策。
- 政策不允许旁加载一次性代码(不允许“雪花代码”)。
- 政策很少允许使用即时编译 (JIT)。
政策验证和加密方案的输入应尽可能简单。
- 政策验证不应需要解析不可信的复杂数据。
可验证执行可在各种硬件上实现,但强烈建议使用硬件不可变信任锚点和防篡改存储空间(例如,存储只能由固件而非内核修改的回滚索引)。
验证阶段
使用哈希和签名范式进行验证后,代码和数据会被视为可信:数据以消息+签名对的形式加载,并使用可信公钥进行验证。消息包含较大代码或数据 blob 的加密哈希,这些哈希会先经过哈希处理和验证,然后才会被视为可信。
第零阶段:硬件到第一个引导加载程序
第 0 阶段包括从硬件到软件的转换。经过验证的执行无法防范硬件攻击,因此在本文档中,我们假定硬件是可信的。在此阶段,不可变代码会根据不可变信任锚点(例如,一次性可编程 (OTP) 内存中的公钥)验证第一个引导加载程序。具体详情因硬件而异。
第一阶段:从第一个引导加载程序到主引导加载程序
第一个引导加载程序通过验证后,就可以验证并执行启动系统所需的其他软件了。这可以是一系列软件映像,其中每个映像都会验证并执行下一个映像,也可以是树状流程。此阶段也因硬件而异。
第 2 阶段:主引导加载程序到预授权代码
主引导加载程序负责验证预授权代码,即产品授权机构明确批准作为该产品核心部分运行的所有代码。预授权代码可以直接在硬件上运行(即无沙盒),并包含 Fuchsia 系统的所有必需元素(例如 Zircon 内核、软件包管理系统、驱动程序)。引导加载程序验证可能因产品而异,但通常使用 Android 启动时验证 (AVB) 版本来验证 Zircon 启动映像 (
从主引导加载程序委托
引导加载程序验证 ZBI 后,有两个关键点会委托验证责任。
- 经过验证的 ZBI 会验证软件包管理系统。具体而言,ZBI 会从引导加载程序接收软件包管理系统的确切说明,并强制执行适用于该系统的所有政策。
- 验证完成后,软件包管理系统将负责验证所有未经预授权且不属于 ZBI 或软件包管理系统的剩余代码。
直接验证与间接验证
直接验证通过精确描述允许运行的软件(例如,使用软件包的加密哈希值)来精确指定要信任的内容。间接验证可指定信任谁,将内容交由委派的机构负责。软件包管理系统接管 VX 政策强制执行器后,可能会使用间接验证来允许动态提交软件,而无需进行完整的系统更新和重新启动。为了维护信任链,必须始终直接验证间接验证流程中涉及的组件和配置(ZBI、软件包管理系统)。
请注意,直接验证的描述可能会有复杂之处。如果将签名附加到包含更多哈希的文件的哈希,系统仍会直接验证所有经过哈希处理的内容。
第 3 阶段:非预授权代码
如果产品所有者未授权相应代码或其签名授权机构,则该代码不受验证,而是受静态政策约束。某些专用设备可能根本无法运行未经预授权的代码。软件包管理系统和组件框架负责强制执行静态政策。
强意图与弱意图
具有明确意图的非预授权代码是指用户(在管理员角色中)明确授权在特定环境中运行的软件包中的代码。此类非预授权代码可以直接在与预授权代码隔离的环境中运行。意图较弱的非预授权代码是指由并非管理角色的用户意外加载或授权的代码(例如,由网页加载的 JavaScript)。必须对意图较弱的非预授权代码进行沙盒或解释,或以其他方式受到严格的限制机制的约束。
实现
以下 Fuchsia 系统对于强制执行经过验证的执行政策至关重要。
主引导加载程序
主引导加载程序实现依赖于 Android 启动时验证来实现验证和内核回滚保护。
BlobFS
BlobFS 是一种加密内容寻址文件系统,专门用于支持经过验证的执行。BlobFS 是可执行代码和关联的只读数据(内核之前的代码、内核及其 bootfs 除外)的唯一存储系统,所有这些数据都存储在 ZBI 中。BlobFS 中的每个 blob 都通过哈希 (Merkle 根) 进行唯一表示和访问,而 Merkle 树结构允许随机访问 blob。从计算上讲,攻击者无法在不更改哈希的情况下更改 Blob。
在第 2 阶段加载 BlobFS 后,验证预授权代码的方法只需检查加载的每个 blob 哈希的签名,并确保 blob 内容与哈希匹配即可。BlobFS 会执行内容验证,并将签名检查委托给软件包管理系统。
软件包管理系统
软件包管理系统(也称为软件交付 [SWD] 堆栈)在 BlobFS 上添加了一层,以便程序员更轻松地处理 blob。SWD 堆栈会将描述软件包的 Merkle 根(具体而言,是软件包的 meta.far 的 Merkle 根)转换为直观易懂的软件包名称。如果任何软件包内容发生更改,meta.far 的 Merkle 根也会随之更改。为简单起见,本文档将使用“软件包哈希”来指代软件包的 meta.far Merkle 根。
SWD 堆栈管理一个名为系统映像的特殊软件包,其中包含所有基础软件包的哈希列表。在第二阶段加载软件包管理系统时,系统映像软件包会由 ZBI 进行验证,因此所有基础软件包都会直接通过验证;也就是说,启动后无需进行签名检查。对于某些专用设备,SWD 堆栈可以配置为仅允许将具有执行权限的基础软件包加载到内存中,这意味着设备将仅执行直接经过验证的代码。
对于间接验证的代码(“Universe 软件包”),可信公钥和与提供它们的委托授权机构有关的其他元数据会包含在启动时验证签名涵盖的位置(例如,ZBI 或基础软件包)。每当加载此类授权机构下载的任何软件包时,都必须通过签名和内容哈希进行验证(而不是在下载时检查一次签名)。“Backstop version”(回退版本)元数据可能会包含在直接验证的数据中,以确保直接和间接验证的软件包都受硬件反回滚保护。
SWD 堆栈还负责下载系统更新。它具有多种控制功能,可防止在设备重启进入更新(并进行验证)之前,正在进行的更新中的任何 blob 可供运行的系统使用。
组件框架
软件包是软件分发的单元,而组件是 Fuchsia 的软件执行标准单元。这种区别的一个后果是,大多数 Fuchsia 组件不会直接与 SWD 堆栈交互。相反,SWD 软件包解析器的主要客户是组件框架,组件框架包含许多组件解析器,这些组件解析器会委托 SWD 堆栈加载代码和数据。某些组件解析器仅限于基本软件包,而其他解析器可能允许访问通用软件包。与其他 Fuchsia 功能一样,这些解析器在组件清单中路由,从而进一步控制其使用情况和可审核性。例如,间接验证的代码只能用于特定环境。