背景
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 阶段:硬件到第一个引导加载程序
第 0 阶段包括从硬件到软件的转换。经过验证的执行无法防范硬件攻击,因此在本文档中,我们假定硬件是可信的。在此阶段,不可变代码会根据不可变信任锚点(例如,一次性可编程 (OTP) 内存中的公钥)验证第一个引导加载程序。具体详情因硬件而异。
第 1 阶段:第一个引导加载程序到主引导加载程序
第一个引导加载程序经过验证后,系统会信任它来验证和执行启动系统所需的其他软件。这可以是一系列软件映像,其中每个映像都会验证并执行下一个映像,也可以是树状流程。此阶段也因硬件而异。
第 2 阶段:主引导加载程序到预授权代码
主引导加载程序负责验证预授权代码,即产品授权机构明确批准作为该产品核心部分运行的所有代码。预授权代码可以直接在硬件上运行(即无沙盒),并包含 Fuchsia 系统的所有必需元素(例如 Zircon 内核、软件包管理系统、驱动程序)。ZBI
从主引导加载程序委托
引导加载程序验证 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 堆栈管理一个名为系统映像的特殊软件包,其中包含所有基础软件包的哈希列表。在第 2 阶段加载软件包管理系统时,系统映像软件包会由 ZBI 进行验证,因此所有基本软件包都会直接进行验证;也就是说,启动后无需进行签名检查。对于某些专用设备,SWD 堆栈可以配置为仅允许将具有执行权限的基础软件包加载到内存中,这意味着设备将仅执行直接经过验证的代码。
对于间接经过验证的代码(“universe 软件包”),经过验证的启动签名涵盖的位置(例如 ZBI 或基础软件包)中包含可信公钥以及有关提供这些公钥的委托授权机构的其他元数据。从此类权威机构下载的任何软件包都必须在每次加载时通过签名和内容哈希进行验证(而不是在下载时检查一次签名)。“回退版本”元数据可能会包含在直接验证的数据中,以确保直接和间接验证的软件包都受硬件反回滚保护。
SWD 堆栈还负责下载系统更新。它具有多种控制功能,可防止在设备重启进入(并验证)更新之前,正在进行的更新中的任何 blob 可供运行的系统使用。
组件框架
软件包是软件分发的单元,而组件是 Fuchsia 的软件执行标准单元。这种区别的一个后果是,大多数 Fuchsia 组件不会直接与 SWD 堆栈交互。相反,SWD 软件包解析器的主要客户是组件框架,其中包含多个组件解析器,这些解析器会委托给 SWD 堆栈来加载代码和数据。某些组件解析器仅限于基本软件包,而其他解析器可能允许访问通用软件包。与其他 Fuchsia 功能一样,这些解析器在组件清单中进行路由,这提供了对其使用情况的额外控制和可审核性。例如,间接验证的代码只能用于特定环境。