RFC-0225:Fxblob:在 Fxfs 中存储 blob

RFC-0225:Fxblob:在 Fxfs 中存储 blob
状态已接受
领域
  • 存储
说明

从 Fxfs 传送 blob,从系统中移除 Blobfs 和 FVM。

问题
  • 122125
Gerrit 更改
  • 889932
作者
审核人
提交日期(年-月-日)2023-07-25
审核日期(年-月-日)2023-09-05

总结

我们提议用 Fuchsia 的新一代文件系统 Fxfs 替换 Fuchsia 的卷管理器 (FVM) 和 blob 存储文件系统 (Blobfs)。系统 blob(包括库、二进制文件和静态配置)将存储在与 Blobfs(内容可寻址、已验证且不可变)具有相同属性的 Fxfs 卷中。

设计初衷

Fxfs 是 Fuchsia 的新一代文件系统,旨在实现性能、可更新性和丰富的功能集。事实证明,它为 Storage 团队工作奠定了坚实的基础,并且越来越多的工程工作集中于增强 Fxfs 上。Blobfs 和 FVM 是在 Fuchsia 早期创建的,并且围绕特定用途构建而成:

  • Blobfs 旨在提供经过验证的、不可变、内容可寻址的存储空间。
  • FVM 旨在让 Blobfs 和 Minfs(一种通用的可变文件系统)共享单个块存储设备,并随着分区的增大从设备进行动态分配。

这些系统在 Fuchsia 的发展过程中发挥着重要作用,但已成为 Storage 团队目标(性能、可更新性和可维护性)的限制因素。

性能改进

提高 Fuchsia 存储堆栈性能方面很多容易实现的目标都是成功,改进现在依赖于对文件系统进行更复杂的更改。

例如,Blobfs 是单线程实现这一事实限制了 Blobfs 的写入性能。向文件系统添加多线程支持并非易事,需要进行重大更改。

由于高写入放大率,Blobfs 的格式对我们来说不利于我们提高性能。存储在不同位置的多个元数据结构必须更新才能写入文件,从而导致写入放大。此外,Blobfs 的日志基于块(而不是 Fxfs 等逻辑日志),在许多情况下,效率较低。

这些工作必须在我们维护的每个文件系统中重复,这大大增加了拥有各种文件系统的成本。能够专注于单个文件系统 (Fxfs) 是有益的,Fxfs 是此提案的关键驱动因素。

我们的 Fxblob 原型已经证明了 Fxfs 的性能优势。 在 Intel NUC 上,Fxblob 具有以下特点:

  • blob 中分页速度提升 55-80%,
  • 写入单个 blob 的速度提高 17%,
  • 同时写入多个 blob 的速度比 Blobfs 快 130%。

并且我们正在进一步增强其性能。

可更新性

Fxfs 的关键设计原则之一是可更新性:我们设计 Fxfs 是为了适应其存储格式随时间的变化。如需了解详情,请参阅 RFC-0136: Fxfs

FVM 和 Blobfs 的设计并未考虑到这一点,并且在实践中,如果不创建格式和实现分支,那么此时对 FVM 或 Blobfs 进行任何重大更改是不可行的。

如果需要添加功能,或者需要优化在磁盘上存储数据的方式,FVM 和 Blobfs 将非常难以使用。下面列举了几个例子:

  • RFC-0005:Blobfs 快照,其中提议更改 FVM 的格式,以提高 Fuchsia 在软件更新的弹性。由于此 RFC 被认定为风险太大且技术上过于复杂,因此无法更改 FVM 格式,因此撤消了此 RFC。
  • Blobfs 中的紧凑 Merkle 树,这是一项节省空间的功能,于 2021 年实现,但由于推出这个功能较为复杂,因此一直未完全发布。C++ Blobfs 中仍支持紧凑布局和旧版布局。

相比之下,Fxfs 的磁盘格式会定期更改,且改动极为简单。在撰写此 RFC 时(2023 年 7 月),Fxfs 的版本为 31,自上次破坏性更改(版本 21,创建日期为 2022 年 6 月 14 日)以来,经历了 10 项格式更改。(请注意,即使这项破坏性更改也可以以向后兼容的方式完成,但出于实际原因,我们选择不这样做。)

可维护性

事实证明,Fxfs 是一个经过充分测试的高效环境,可供工程师使用。它在 Rust 中实现,具有广泛的单元、集成和模糊测试覆盖范围,并且封装了更细微或更棘手的问题(层文件合并、日记、事务和锁定等)。此外,由于 Fxfs 的层文件一旦创建便不可更改,并且元数据更新会附加到内存中的层文件(和日志)上,而不是逐个写入磁盘,这一事实使得处理并发问题变得容易得多。

相比之下,FVM 存在严重的技术债务、缺乏强大的测试覆盖率,并且其格式封装不当(Fuchsia 中的大量软件解读 FVM 格式),所有这些因素使得它成为一个非常难以更改的系统。

性能改进部分所述,大多数改进都必须在我们维护的每个文件系统实现中重复,这会成倍增加改进的成本。这也适用于维护费用。需要维护的软件越少,所需的工程工作量就越少。

请注意,我们在一段时间内无法完全弃用 FVM 和 Blobfs,因为现有产品依赖于它们。但是,我们希望 FVM 和 Blobfs 在现有产品中足够稳定,因此维护费用微乎其微。

利益相关方

教员

abarth@google.com

审核者

咨询人员

社交

该设计首先面向相关利益相关方进行了社会化(早期参与了软件交付,在初始原型得到更充分的充实后,又涉及安全/内核)。

要求

  • Fxblob 必须提供与 Blobfs 语义完全匹配的文件系统 API。也就是说:
    • Fxblob 必须允许创建静态文件(一次写入、读取多次)。
    • Fxblob 必须具有内容可寻址的文件名(即文件的名称必须是其内容的 Merkle 根)。
    • 为了实现可移植性并简化迁移,应使用来自 Blobfs 的相同 Merkle 树算法(即 Blobfs 中的文件名应与 Fxblob 中的文件名一致)。
    • Fxblob 必须确保文件的内容与其 Merkle 根哈希匹配,然后才能允许客户端读取这些文件。
    • Fxblob 必须在包含系统上每个 blob 的 pkg-cache 中提供扁平目录结构。
    • Fxblob 必须支持可执行文件和分页。
  • Fxblob 必须支持 RFC-0207 中指定的“传送 blob”有线格式。
  • Fxblob 在各个维度的性能应该与 Blobfs 相当或更好。
    • 在延迟时间和吞吐量方面,Fxblob 的读写性能应绝对优于 Blobfs。
    • Fxblob 的磁盘用量和内存用量应与 Blobfs 相当。
  • Fxblob 必须具有精确的核算机制,以便能够预先确定存储空间预算,并根据系统上已知存在的其他 blob 在可用空间上确定合理的范围。
  • Fxblob 应最大限度地减少可信代码库 (TCB) 的扩展,即,对于 Fuchsia 的“Verified Execution”的安全属性,必须确保是正确的所有代码集。Fxfs 不需要可信,而可信操作应委托给其他位置。

设计

从架构的角度来看,目标是从图 1 转到图 2:

老建筑

图 1:Fuchsia 设备的当前存储架构。

新架构

图 2:适用于 Fuchsia 设备的 Fxblob 存储架构。

Fxfs 中的“blob”卷将是一个模拟 blobfs 属性(内容可寻址、不可变性等)的特殊卷。对于系统的上层,此项更改是透明的,但在系统的许多较低级别的部分中,更改是必不可少的:

  • 系统组装和构建工具
  • 设备引导软件和工具(铺路和刷写)、
  • 恢复软件
  • Fshost,
  • Fxfs,用于支持新 blob 分区的特殊属性,
  • Pkg-cache 和 pkg-resolver,使用一组新的 API 来读取和写入 blob。

对 Fxfs 的更改

Fxfs 已经能够很好地适应这种架构,它具有对卷的原生支持,并且具有专为灵活性而设计并且可以随时更改的格式。

原型实现已完成(请参阅 //src/storage/fxfs/platform/src/fuchsia/fxblob),它会实现满足 blob 存储属性的特殊卷。卷具有一个根目录,在该目录中,无法使用 fuchsia.io 协议创建文件或目录。相反,该卷提供了一个特殊的 BlobWriter 协议,pkg-cache 将使用该协议将新的 blob 安装到卷。

此卷中的文件可以使用 blobfs 使用的相同 Merkle 树算法(文件名为根哈希)进行内容寻址。Merkle 树是在安装 blob 时生成的,Fxfs 可确保 Merkle 根目录与文件名匹配,然后才保存文件。每当从磁盘读取文件的某一部分时,Fxfs 都会对照文件的 Merkle 树验证内容,以确保完整性。

Fshost 的变化

Fshost 是一个低级组件,可检测块设备并装载其包含的文件系统,从而提供与 /blob 和 /data 到系统其余部分的连接。

当 Fshost 通过静态配置配置为预期采用 Fxblob 格式的系统时,它会将 /blob 目录连接到 Fxfs 实例中的 blob 卷,并将 /data 目录连接到 Fxfs 实例中的数据卷。Fshost 将继续支持 FVM+Blobfs 的旧版配置。

对系统组装和构建工具的更改

系统组装是 Fuchsia build 的最后一步,在这一步骤中,工件会组合成一组映像和清单,这些映像和清单构成了一个 Fuchsia 系统映像。

系统组件的输出之一是 FVM+Blobfs 映像,其中包含系统中“基本”的 blob 集。我们将调整系统组件,使其也能够发出包含这些 blob 的 Fxfs 格式的映像,具体取决于构建时配置。

我们还会以“稀疏”格式发出 Fxfs 映像,这样可以更高效地传输到设备。使用的稀疏格式是 Android 稀疏格式,它已受 fastboot 支持,因此是一种自然的选择。

设备引导的变更(铺路和刷写)

虽然已正式弃用设备引导的铺装功能 (RFC-0075: Deprecate zedboot-based paving),但它的使用范围仍然足够广泛,因此我们已选择将对 Fxfs 映像的支持添加到铺砌工作流中。

刷写是现代设备引导工作流,它依赖于 fastboot 协议。与铺装(解读它收到的 FVM 图像)相比,此协议相对简单,因此是一项简单直接的更改。。

上述稀疏映像格式会同时用于铺路和刷写,以减少传输到设备的字节数并减少内存占用量。

对软件包安装和 OTA 路径的更改

与 blobfs 不同,Fxblob 不支持 fuchsia.io 写入路径(该写入路径存在明显的性能问题)。相反,fxblob 支持新的写入 API,该 API 利用共享 VMO(而不是 FIDL)处理其数据平面,从而减少了写入 blob 所需的 FIDL 往返总次数。

新的 API 分为两个协议:fuchsia.fxfs.BlobCreator 和 fuchsia.fxfs.BlobWriter。BlobCreator 是一种可发现的协议,pkg_cache 可使用该协议创建新 Blob(如果尚无这些 Blob),而 BlobWriter 是一种私有协议,用于协助将 Blob 数据流式传输到 Fxfs。

共享 VMO 以环形缓冲区的形式进行组织,使我们能够流水线写入请求。Pkg_cache 填满环形缓冲区的一部分,然后使用 BlobWriter.BytesReady FIDL 方法将某些字节可用时通知 Fxfs。Fxfs 会等待这些通知,从环形缓冲区加载一些数据,计算该数据的 Merkle 哈希,然后将数据流式传输到磁盘。

当最终的 BytesReady 请求发送到 Fxfs 时,Fxfs 会阻止该请求,直到根据接收的内容全面计算并验证 Merkle 树;如果哈希值和内容不匹配,Fxfs 将使此请求失败并返回 ZX_ERR_IO_DATA_INTEGRITY。Fxfs 将确保最终调用完成后该文件可见(即对 fuchsia.io.Directory.ReadDirents 可见)。

创建了一个客户端库来封装环形缓冲区管理。

该流程如下所示:

Blob 安装路径

Merkle 树验证、解压缩和 VMEX 处理方面的变更

Blobfs 目前包含 VMEX 功能,负责在需要时创建可执行 VMO,并在读取时解压缩 blob 内容。由于 VMEX 允许执行任意代码,因此 blobfs 还依靠沙盒解压缩程序在读取时解压缩 blob 内容。(Blob 内容是由外部提供的,因此可被视为不受信任的数据。)与解压缩相关联的此类额外的跃点和上下文切换会显著降低性能。

借助 Fxblob,我们的目标是优化分页路径的性能,这需要(以及其他)将解压缩移到 Fxfs 进程本身中。但是,在高度受信任的组件中积累更多功能可能会出现潜在的安全问题。我们相信可以通过取消对 Fxfs 的信任来解决这些顾虑。

为实现此目标,需要将两项可信操作从 Fxfs 中移出:

  1. 对照 Merkle 树验证文件内容;
  2. 使用 VMEX 资源创建可执行页面。

为避免 Fxfs 保留 VMEX 功能,pkg-cache 将接管此职责。鉴于 pkg-cache 必须是可信的,可以从 blob 名称映射到哈希,因此这不会对分配给该组件的信任范围产生实质性影响,并且让 Fxfs 不被视为高信任组件。

我们还需要为 /bootstrap/base_resolver 提供 VMEX 资源;此组件会提供基本集之外的软件包。它与 pkg-cache 类似(实际上,它的大部分业务逻辑都使用相同的库)。

为了使 Fxfs 完全不受信任,我们还需要将 Merkle 验证移至其他位置(尽管 Fxfs 仍会存储并提供 Merkle 树内容)。这里有一些选项:

  1. 创建一个仅验证数据且可信度极高的单独组件。
  2. 将此功能移到已得到高度信任的内核中。

请注意,pkg-cache 在打开 blob 时需要直接与可信的验证程序进行通信,或者需要能够直接查询此可信验证程序,以便将验证程序使用的根哈希与给定 blob 的预期哈希进行比较。这样,即使遭到入侵的 Fxfs 完全更改了数据和 Merkle 树(因此验证工具会将被篡改的数据视为正确的),pkg-cache 也能够检测出篡改行为并拒绝文件内容。

选择默克尔验证的位置归结为在安全性和性能之间进行权衡,值得详细探索。因此,这将在单独的 RFC 中解决,并且目前,验证在 Fxfs 中进行。

请注意,Fuchsia 将来也需要支持另外两项功能,它们也依赖于 Merkle 树:Fs-verity 和 dm-verity,一些 Linux 应用会分别使用它们来实现文件和映像完整性。Fuchsia 计划在 RFC-0082:在 Fuchsia 上运行未经修改的 Linux 程序中支持这两项功能。在设计进程外 Merkle 验证时,也需要考虑这些用例。

实现

我们采取了实现功能原型的方法,并使它们在测试和开发环境中能够进行“泡泡”,这一过程到目前为止表现良好。我们打算继续为将来的工作和未完成的功能采用这样做(如需了解与 Merkle 验证相关的工作,请参阅“安全性”部分)。

功能将受到在构建时启用的功能标志控制。例如,pkg-cache 将检测新的 Fxblob 读取和写入 API 是否存在,并在可用时优先使用它们,否则将回退到通常的 fuchsia.io 机制。

性能

对我们的原型进行基准测试得出的初步结果表明,读写性能有所提升。在 Intel NUC 上,Fxblob 具有以下特点:

  • blob 中分页速度提升 55-80%,
  • 写入单个 blob 的速度提高 17%,
  • 同时写入多个 blob 的速度比 Blobfs 快 130%。

其他重要的性能方面包括内存消耗和存储空间用量。我们正在分析这些方面,并解决 Blobfs 和 Fxblob 之间的任何重大问题。一般来说,Rust 组件往往会更耗用内存,但 Blobfs 中大多数内存使用量是由于缓存页面而引起的,这对于 Fxblob 来说是相同的费用。

Fxfs 的存储空间用量比 Blobfs 更复杂,这是因为 Fxfs 将元数据更新作为一系列层文件存储在一组层文件中,而不是对 inode、ends 等进行固定的分配。但是,我们也支持因移除 FVM(需要一些磁盘空间来释放自己的元数据,并且可提高其内部 FragmentF 的灵活性)来释放更多存储空间,从而提升其内部 FragmentF 的性能。在编写此 RFC 时,我们的原型实现使用相当数量的磁盘空间将等效数量的 blob 存储为 Blobfs。

工效学设计

这些更改对绝大多数 Fuchsia 组件都是透明的。目前,只有 pkg-cache 会与 Blobfs 直接交互。

向后兼容性

我们将继续支持旧版配置 (FVM + Blobfs) 中的系统,这些系统将支持已发布的产品。在相关产品终止服务前,我们将继续支持旧版配置(以及相应的测试基础架构)。

包括大多数开发者在内,未来的所有 Fuchsia 产品都将使用此新配置。

未规划迁移路径;设备将使用 Fxblob 或使用旧版配置。

安全注意事项

Blobfs 是 Fuchsia 中备受信赖的组件,在 Fuchsia 的“Verified Execution”中发挥着重要作用,因为它可以验证系统中几乎所有其他组件的软件完整性。(存储在 bootfs 中的低级别组件将通过不同的方式进行验证)。此外,blobfs 能够创建可执行页面,由于其他系统组件信任 blobfs 提供的内容,因此 blobfs 可以将任意代码插入任何组件的地址空间中,使 blobfs 能够模拟任何依赖 Blobfs 作为可执行代码的组件的功能。

为了缓解 Blobfs 中的这些风险,我们采取的一种策略是对某些操作进行沙盒化处理。例如,由于存在解析不可信输入的风险,解压缩已移至单独的进程。如果沙盒化解压缩程序遭到入侵,最严重的后果就是拒绝服务攻击,因为 Blobfs 只会拒绝该解压缩程序返回的任何无效数据。我们认为在 Fxfs 的读取路径上不需要采用相同的策略,因为本地攻击的风险较低,但我们将探索在写入路径上实现类似的沙盒策略来防范远程攻击。

虽然从安全性的角度来看,Fxfs 比 Blobfs 具有一些优势(例如,它在内存安全型 Rust 中实现),但仍然比 C++ Blobfs 复杂得多,而且复杂性也带来了风险,因此需要采取更多措施来缓解风险。

上文的“设计”部分介绍了我们提议的缓解措施。

安全团队指出,基于共享内存的 IPC 机制(即用于 blob 写入的环形缓冲区)需要特别注意,以确保读取器和写入器对于不协作的对等端具有稳健性,尤其是在共享内存被映射的情况下。我们将负责设计这些协议,并根据需要咨询安全团队。

入侵 Fxfs 的结果

由于此方案扩大了 Fxfs 的受攻击面,因此值得记录能够入侵 Fxfs 的攻击者能够执行的操作(以及无法执行的操作)。

Fxfs 不会在此方案中包含 Vmex 资源,虽然 Fxblob 会为系统中大多数软件包提供二进制文件和库的(可执行)内容,因为这些内容将由另一个组件(内核或某些用户空间组件)独立进行验证,但 Fxfs 无法伪造可执行页面,Fxfs 也无法运行其他进程的数据。

因此,威胁模型涵盖 Fxfs 直接有权访问的内容:

  • Fxfs 可以任意读取和写入数据文件内容。这可能会导致用户数据的敏感信息泄露,或者可能会利用无法验证其存储数据的其他组件中的 bug。
  • Fxfs 可以删除 blob 内容,这会对其他组件造成拒绝服务攻击。
  • Fxfs 可以通过拒绝 blob 写入来阻止系统更新。
  • Fxfs 可与块设备驱动程序交互,并可能会利用其中的漏洞获取驱动程序具有 Fxfs 不具备的任何功能。

隐私注意事项

不适用。

测试

我们已实现此方案的原型,它可以启动 Fuchsia 并执行所有常用系统功能。此原型在 CQ 中的构建器上启用,该构建器运行常规系统测试套件,以确保我们真实使用和覆盖原型。

最终,我们会扩展此功能,以将其用于更多上下文(例如,在特定产品上启用该功能,开发者将自然地使用它,而 CQ/CI 中也会更多地使用)。在面向任何用户设备上使用此架构之前,预计会经历很长的浸泡时间。

此外,还针对相关功能添加了常见的单元测试、集成测试和 e2e 测试金字塔测试。我们还实现了自动化基准测试来跟踪从 Fxblob 读取和写入的性能,并将其与 blobfs 的性能进行比较。

文档

一些存储文档将需要作为此方案的一部分进行更新。例如,应更新文件系统架构文档。

缺点、替代方案和未知情况

缺点:设备发生死机的风险

在旧版架构中,minfs 中的 bug 只会影响 minfs;这是因为系统的重要部分(二进制文件、库、配置等)存储在 blobfs 中,因此即使数据分区重置,系统仍会保持正常运行状态。

将 blob 和数据合并到单个文件系统中,意味着 Fxfs 中的 bug 可能会损坏设备,因此不存在相同的安全网。

我们相信 Fxfs 足够强大,可以充当 Fuchsia 基础架构的关键部分。自 2021 年以来,Fxfs 就已经过广泛测试和使用。文件系统损坏的概率很低。

如果确实发生损坏,最佳做法与其他操作系统类似:提供可用于将设备恢复到可正常运行状态的恢复路径。能够从恢复 (fxrev.dev/563864) 执行 OTA 可以解决此问题,作者支持这种问题(因为它提供了针对更广泛的问题修复设备的机会)。

请注意,这不是 Fuchsia 特有的风险。大多数其他操作系统都有一个全局文件系统来存储所有数据(无论是否重要)。

替代方案:依靠 zx::streams 加快 blob 写入速度

为 Fxblob 创建的自定义写入 API 与 zx::streams 有一些相似之处,zx::stream 使用 VMO 作为共享数据缓冲区,并使用页面故障作为控制机制。不过,流的设计能够适应更广泛的用例(一般文件 I/O),并且包含更多问题 - 内容大小跟踪、页面故障开销、I/O 批处理更少(因为错误是页面粒度)。专门构建的 API 可以更简单、更高效。

Blob 写入性能足够重要(因为它是系统 OTA 和安装大型软件包的关键瓶颈),并且与一般文件 I/O 相比具有足够的特定限制,因此我们认为自定义接口值得努力。我们前景可观的成效印证了这项工作的成效。

早期技术和参考资料

可以说,此方案使 Fuchsia 更接近大多数使用通用文件系统来实现更广泛的目的的操作系统,并提供了专门的功能,例如内容可寻址和使用其他机制进行验证。虽然我们会为此方案向 Fxfs 添加一些特殊功能,但大部分工作是在 Fxfs 的外层进行的;核心文件系统保持不变。

例如,在 Linux 上,通用文件系统可以与 dm-verity 或 fs-verity 结合使用来提供内容验证(分别为映像和特定文件)。