RFC-0206:废弃存储

RFC-0206:弃用 stash
状态已接受
区域
  • 安全
  • 存储
说明

弃用 stash 服务并迁移客户端以使用存储功能

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-12-08
审核日期(年-月-日)2022-01-13

摘要

Stash 服务已不再维护,并且当前实现无法提供最初创建时旨在提供的属性。此 RFC 提议弃用暂存。我们将使用 stash 将客户端迁移到 Fuchsia 的标准持久存储功能,然后删除为 stash 服务提供支持的三个组件。

设计初衷

Stash 是一项简单的持久性服务,最初是为需要在启动过程早期(通常是在软件更新之前)访问持久性可变状态的平台组件而创建的。

如下文更详细的讨论所示,存在三种不同的 stash 协议变体。所有变体都使用相同的底层 FIDL 协议,并且使用相同的二进制文件实现。本文档使用“暂存”一词来涵盖所有这些变体。

每次 Fuchsia 在软件更新之前读取持久性可变状态时,攻击者都有可能借此机会保持其访问权限:如果攻击者在之前的电源周期中获得了对设备的完全控制权,我们必须假设他们能够将任意数据写入设备的持久性可变状态。读取可变数据的代码路径中的漏洞可能会让受攻击者控制的数据在下次电源循环中利用系统。如果在启动过程中过早发生读取,后果可能会更加严重;攻击者可能会阻止软件更新,从而阻止漏洞得到修补。

Stash 旨在通过提供简单且安全的持久性可变存储空间来降低此场景的风险,以便在早期启动中使用。Stash 本应具有以下三个属性:

  1. 极简 - Stash 应使用比完整存储堆栈小得多且简单得多的代码库,从而更易于检查实现并降低 bug 风险。
  2. 易于使用 - Stash 应该易于客户端使用,从而降低其与持久可变状态集成时出现 bug 的风险。
  3. 安全 - 由于 stash 的存在是为了提高系统的安全性,因此其设计和实现不应引入新的安全问题。

目前,stash 无法提供以下大部分属性:

  1. 最小 - Stash 实现为 FIDL 服务器,位于序列化层之上,位于标准文件系统(fxfs 或 minfs,位于 zxcrypt 之上)之上。Stash 并不比标准存储堆栈更简单。从理论上讲,未来可以在不更改 FIDL 接口的情况下迁移到更简单的存储实现,但目前还没有这样做的计划。
  2. 易于使用 - Stash 主要旨在实现此目标。其 FIDL 接口采用简单的键值范式,数据类型数量有限。这样一来,客户端代码通常会比较简单,并降低客户端代码中出现 bug 的可能性。需要处理向后兼容性、事务性写入和 FIDL 错误确实会带来一些复杂性,并且在某些情况下,客户端仍然需要编写自己的辅助库,例如 wlan
  3. 安全 - Stash 的设计假定组件框架会提供客户端身份,让 FIDL 服务器能够识别其客户端。组件框架不提供客户端身份,并且在可预见的未来也没有引入该功能的计划。这意味着不同的 stash 客户端可以读取和写入彼此的数据,而该协议依靠诚信系统来避免这种情况。

虽然 stash 最初仅用于支持“早期启动”组件,但 Fuchsia 上没有“早期启动”阶段的正式定义,也没有哪些组件被视为“早期启动”组件的列表。stash 的多个客户端在启动过程中不会过早启动,并且没有现有客户端在引导过程中启动。

自 2020 年以来,Fuchsia 一直在管理一个复杂的复制和隔离系统,以解决上述安全问题(请参阅 https://fxbug.dev/42124367)。

  • 已定义三种不同的 FIDL 存储协议:fuchsia.stash.Storefuchsia.stash.Store2fuchsia.stash.SecureStore
  • 存在三种不同的 stash 组件,每种组件都作为单独的进程运行,并提供单独的协议。
  • 我们会仔细为 Stash 客户端分配三种协议之一,并评估共享同一渠道的客户端,以确保它们读取或写入彼此数据的风险可接受。
  • 存在 build 可见性列表,以防止在未进行安全审核的情况下添加新的 stash 客户端。

这种情况导致了持续的混乱和工程成本,而其性能和安全属性却几乎不够。

利益相关方

辅导员

  • hjfreyer

审核者

  • atait (DHCP)
  • brunodalbo (Netstack)
  • ecstone(迁移)
  • emircan(Scenic)
  • jamuraa(蓝牙)
  • nmccracken (WLAN)
  • palmer(安全性)
  • senj(Omaha 客户端)
  • paulfaria(设置服务)

已咨询

  • silberst、wittrock、shayba、cgonyeo、erahm、mnck、jfsulliv

共同化

此 RFC 的早期草稿是在与所有受影响的客户端的利益相关者合作开发完成的。

设计

我们打算弃用 stash,并将大多数现有客户端迁移到其他组件使用的“数据”存储功能。存储功能通过标准文件系统 API 进行访问。对于大多数组件,此迁移将涉及使用序列化库将组件的持久性数据结构转换为字节流,然后组件将这些字节流写入磁盘。读取持久性数据将涉及从磁盘读取文件,然后使用同一库来反序列化并填充组件的持久性数据结构。

Scenic 未按预期用途使用 stash(请参阅 https://fxbug.dev/42173164),我们将把其使用情形迁移到结构化配置开发者替换项

始终需要尽可能缩小在启动早期启动且可能会影响软件更新成功率的组件的攻击面。目前依赖于 stash 的网络和 SWD 组件在使用数据存储功能时应谨慎,但我们不会为这些组件单独维护一套存储访问要求。安全地将数据持久保存到存储功能的最佳实践包括:

  • 尽可能减少持久化数据量。
  • 使用范围较窄且精确定义的数据类型。
  • 使用经过安全审核的序列化库来打包和解包数据。

可以根据所需的存放属性对该设计进行评估,如下所示:

  1. 最低 - 总体复杂程度与现状类似:仍使用序列化库(尽管使用位置从通用位置移至每个客户端)。Fxfs(或 minfs 和 zxcrypt)仍然存在。未来仍有可能通过更简单的文件系统来支持存储功能,但这样做可能需要更改客户端。
  2. 易于使用 - 使用起来与当前情况类似:客户端必须与序列化库交互并执行基本的文件系统操作,而不是管理 FIDL 连接、事务和故障。目前已有多种使用存储功能来持久保存 Fuchsia 组件状态的实现,这些实现可以重复使用。
  3. 安全性 - 安全性得到提升:该设计可保证组件之间的隔离。我们使用已通过安全审核并已在生产环境中使用的现有 Fuchsia 技术。

此外,该设计还改进了其他几项属性:

  • 由于我们移除了三个组件实例,因此资源利用率较低
  • 现在,您可以更轻松地将磁盘利用率归因于使用它的组件

实现

如果此提案获得批准,我们将明确记录存储组件和所有存储协议已弃用。

然后,我们将与七个受影响的客户端组件中的每一个合作,就迁移计划和大致时间表达成一致意见。在某些情况下,团队已计划从 stash 迁移(例如 https://fxbug.dev/42172963),而在其他情况下,可信平台服务团队可以帮助完成迁移。此 RFC 未指定完成迁移的截止日期。

在 stash 中存储关键数据的组件需要通过过渡版本才能完成迁移(即在软件升级过程中,每个设备都必须经过的软件版本)。在此过渡版本中,组件将能够从暂存区或存储功能读取其持久状态,但会写入到存储功能。通过此过渡阶段可确保在组件移除其读取 stash 的代码之前,数据已迁移到存储功能。

在与 stash 客户合作规划迁移时,我们力求尽可能减少所需的平台过渡版本数量。

当特定 stash 协议的所有客户端都完成迁移后,该协议和提供该协议的 stash 实例将被删除。该存储二进制文件将随最后一个协议一起删除。

性能

此提案将通过删除三个组件实例来减少磁盘、内存和 CPU 利用率。

安全注意事项

此提案通过以下方式提高了 Fuchsia 的安全性:保证早期启动组件不再能够读取和写入彼此的持久状态,并消除当前未维护的代码。

隐私注意事项

此提案不会改变收集和存储的用户数据。隐私保护方面有小幅改进,因为遭到入侵的早期启动组件无法再读取由其他组件存储的 PII 数据。

测试

现有的端到端测试和集成测试涵盖了使用 stash 持久存储和检索可变状态。这些相同的测试将涵盖使用基本存储功能存储和检索此状态。每个暂存客户端都应添加集成测试,以验证从暂存到存储功能的迁移。

文档

迁移完成后,现有暂存区文档将被删除。

考虑的替代方案

替代方案 1:使用新的“基本”存储功能

当前提案建议使用现有的“数据”存储功能。一种类似的解决方案是创建一个新的“基本”存储空间,专门供早期启动组件使用。

使用单独的存储功能可让我们跟踪最终应使用更简单存储解决方案的组件。使用“基本”存储功能的组件遵循一组旨在降低复杂性、减少 bug 风险并简化向未来替代后端迁移的最佳实践。最佳实践可能包括:

  • 使用获批的序列化库来打包和解包数据。例如,Rust 组件的 serde
  • 以原子方式更新文件内容。例如,通过写入临时文件,然后将临时文件重命名为最终路径
  • 不创建子目录
  • 请勿创建大于 X kB 的文件
  • 创建的文件数量不得超过 Y

许多此类最佳实践都可用于简化替代文件系统,以支持“基本”存储功能。例如,如果客户端不使用目录,则可以更轻松地迁移到不支持目录的简单文件系统。

安全团队会维护一份列表,其中列出了哪些组件被视为“早期启动”组件。自动化工具会验证这些组件是否仅使用“基本”存储功能,并在可行的情况下验证它们对文件系统的使用是否符合最佳实践。

此解决方案的初始实现将非常简单:“基本”可以由现有“数据”fxfs 或 minfs 分区上的新子目录提供支持。不过,要保持“早期启动”定义的一致性,并构建相应工具来强制执行这些早期启动组件中的数据持久性模式,需要付出巨大的流程和工具成本。

Fuchsia 并不打算实现一个更简单的文件系统来支持“基本”存储功能。如果没有不同的文件系统,花费资源来维护由同一实现支持的两组不同的客户端几乎没有任何好处,因此未选择此解决方案。

替代方案 2:为早期可变状态编写专用客户端库

当前提案建议使用成熟的现有库来序列化可变持久状态。另一种方法是使用每种目标语言(目前为 Rust 和 Go,未来可能为 C++)编写新的客户端库。

专用客户端库可以轻松强制执行最佳实践,并且比现有的通用序列化库(如 serde)更小、更易于使用。不过,设计和实现这些新库会大幅增加此提案的工程成本。现有客户端使用 stash 的目的各不相同,并且使用的封装容器也不同,因此单个客户端库可能无法满足所有这些客户端的期望。Stash 目前无人维护,如果致力于设计和实现新的客户端库,可能会将迁移延迟几个季度。

在先技术和参考资料