| RFC-0206:弃用 stash | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 弃用 stash 服务,并将客户端迁移到使用存储功能 |
| 问题 | |
| Gerrit 变更 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2022-12-08 |
| 审核日期(年-月-日) | 2022-01-13 |
摘要
stash 服务无人维护,当前实现不提供其最初创建时要提供的属性。此 RFC 提议弃用 stash。我们会将使用 stash 的客户端迁移到 Fuchsia 的标准永久性存储功能,然后删除为 stash 服务提供支持的三个组件。
动机
Stash 是一种简单的持久化服务,最初是为了供需要在启动过程早期(通常在软件更新之前)访问持久化可变状态的平台组件使用。
如下文所述,stash 协议有三种不同的变体。所有变体都使用相同的底层 FIDL 协议,并使用相同的二进制文件实现。本文档使用“stash”一词来涵盖所有这些变体。
每次 Fuchsia 在软件更新之前读取永久性可变状态时,攻击者都有可能借机持久化其访问权限:如果攻击者在上一个电源周期中获得了对设备的完全控制权,我们必须假设他们能够将任意数据写入设备的永久性可变状态。如果读取可变数据的代码路径中存在漏洞,攻击者控制的这些数据可能会在下一个电源周期中利用该漏洞攻击系统。如果读取操作发生在启动过程的早期,后果可能会更加严重;攻击者可能会阻止软件更新,从而阻止漏洞被修补。
Stash 旨在通过提供简单且安全、可在前期启动中使用的持久性可变存储空间,来降低出现这种情况的风险。Stash 旨在具有三个属性:
- 极简 - Stash 应使用比完整存储堆栈小得多且更简单的代码库,从而使实现更易于审核并降低出现 bug 的风险。
- 易于使用 - Stash 应易于客户端使用,从而降低其与持久可变状态集成时出现 bug 的风险。
- 安全 - 由于 stash 的存在是为了提高系统的安全性,因此其设计和实现不应引入新的安全问题。
目前,stash 无法提供以下大部分属性:
- 极简 - Stash 是作为 FIDL 服务器实现的,位于序列化层之上,而序列化层位于标准文件系统(fxfs 或 minfs,位于 zxcrypt 之上)之上。Stash 并不比标准存储堆栈简单。理论上,将来可以迁移到更简单的存储实现,而无需更改 FIDL 接口,但目前没有这样做的计划。
- 易于使用 - Stash 主要满足了这一目标。其 FIDL 接口公开了一个简单键值对范式,其中包含有限数量的数据类型。这通常可以使客户端代码保持简单,并降低客户端代码中出现 bug 的可能性。处理向后兼容性、事务性写入和 FIDL 错误的需求确实会带来一些复杂性,在某些情况下,客户端仍然需要编写自己的辅助库,例如 wlan。
- 安全 - 存储区的设计假设组件框架会提供客户端身份,让 FIDL 服务器能够识别其客户端。组件框架不提供客户端身份,并且在可预见的未来没有引入客户端身份的计划。这意味着不同的 stash 客户端可以读取和写入彼此的数据,并且该协议依赖于荣誉系统来避免这种情况。
虽然 stash 最初仅用于支持“前期启动”组件,但 Fuchsia 上没有前期启动阶段的正式定义,也没有哪些组件被视为“前期启动”组件的列表。stash 的多个客户端不会在启动过程的早期启动,并且现有客户端都不会在引导期间启动。
自 2020 年以来,Fuchsia 一直在管理一个复杂的复制和隔离系统,以解决上述安全问题(请参阅 https://fxbug.dev/42124367)。
- 定义了三种不同的 FIDL 存储协议:
fuchsia.stash.Store、fuchsia.stash.Store2和fuchsia.stash.SecureStore。 - 存在三种不同的 stash 组件,每种组件都作为单独的进程运行,并服务于单独的协议。
- Stash 客户端会谨慎地分配到三种协议之一,并且会评估共享通道的客户端,以确保它们读取或写入彼此数据的风险在可接受范围内。
- BUILD 可见性列表用于防止在未经安全审核的情况下添加新的 stash 客户端。
这种情况导致了持续的混乱和工程成本,而其性能和安全属性几乎无法满足要求。
利益相关方
辅导员:
- hjfreyer
Reviewers:
- atait (DHCP)
- brunodalbo (Netstack)
- ecstone(迁移)
- emircan(风景)
- 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 组件在使用数据存储功能时应谨慎,但我们不会为这些组件维护单独的一组存储访问权限要求。将数据安全地持久保存到存储功能的最佳实践包括:
- 尽可能减少持久化的数据量。
- 使用范围较窄且定义精确的数据类型。
- 使用经过安全审核的序列化库来打包和解包数据。
可以根据所需的 Stash 属性对该设计进行评估,如下所示:
- 极小 - 总体复杂性与现状类似:仍使用序列化库(尽管使用位置从公共位置移至每个客户端)。Fxfs(或 minfs 和 zxcrypt)仍然存在。未来仍有可能通过更简单的文件系统来支持存储功能,尽管这样做可能需要更改客户端。
- 易于使用 - 易用性与现状类似:客户端必须与序列化库交互并执行基本文件系统操作,而不是管理 FIDL 连接、事务和故障。目前有几种使用存储功能持久保存 Fuchsia 组件状态的现有实现,这些实现可以重复使用。
- 安全性 - 安全性得到提高:该设计可保证组件之间的隔离。我们使用已通过安全审核且已在生产环境中使用的现有 Fuchsia 技术。
此外,该设计还改进了其他几项属性:
- 由于我们移除了三个组件实例,因此资源利用率较低
- 更容易将磁盘利用率归因于使用它的组件
实现
如果此提案获得接受,我们将明确记录 stash 组件和所有 stash 协议已弃用。
然后,我们将与 7 个受影响的客户端组件中的每一个合作,就迁移计划和大致时间表达成一致。在某些情况下,团队已经计划从 stash 迁移(例如 https://fxbug.dev/42172963),在其他情况下,可信平台服务团队可以帮助迁移。此 RFC 未指定完成迁移的截止日期。
在 stash 中存储关键数据的组件需要通过过渡版本来完成迁移(即每个设备在软件升级过程中都必须经过的软件版本)。在此过渡版本中,组件能够从存储区或存储功能读取其持久状态,但会写入存储功能。通过此过渡版本,可确保在组件移除其用于读取 stash 的代码之前,数据已迁移到存储功能。
在与 Stash 客户合作规划迁移时,我们的目标是尽量减少所需的平台过渡版本数量。
特定 Stash 协议的所有客户端完成迁移后,该协议和为其提供服务的 Stash 实例将被删除。stash 二进制文件将与最后一个协议一起删除。
性能
此提案将通过删除三个组件实例来降低磁盘、内存和 CPU 利用率。
安全注意事项
此提案通过确保前期启动组件无法再读取和写入彼此的持久状态,并消除当前未维护的代码,从而提高 Fuchsia 的安全性。
隐私注意事项
此提案不会改变所收集和存储的用户数据。 隐私保护方面略有改进,因为遭到入侵的前期启动组件无法再读取其他组件存储的 PII 数据。
测试
现有的端到端测试和集成测试涵盖了使用 stash 存储和检索持久性可变状态。这些测试将涵盖使用基本存储功能存储和检索此状态。每个 stash 客户端都应添加集成测试,以验证从 stash 到存储功能的数据迁移。
文档
迁移完成后,现有暂存文档将被删除。
考虑过的替代方案
方案 1:使用新的“基本”存储功能
当前提案建议使用现有的“数据”存储功能。类似的解决方案是创建一个新的“基本”存储空间,专门供前期启动组件使用。
通过使用单独的存储功能,我们可以跟踪最终应使用更简单存储解决方案的组件。使用“基本”存储功能的组件遵循一系列最佳实践,旨在降低复杂性、减少 bug 风险,并简化向未来替代后端的迁移。最佳实践可能包括:
- 使用经过批准的序列化库来打包和解包数据。例如,适用于 Rust 组件的 serde
- 以原子方式更新文件内容。例如,通过写入临时文件,然后将临时文件重命名为最终路径
- 不创建子目录
- 不要创建大于 X kB 的文件
- 创建的文件数不得超过 Y 个
其中许多最佳实践都融入了我们可以在替代文件系统中进行的简化,以支持“基本”存储功能。例如,如果客户端不使用目录,则更容易迁移到不支持目录的简单文件系统。
安全团队会维护一份被视为“前期启动”的组件列表。自动化工具会验证这些组件是否仅使用“基本”存储功能,并在可行的情况下验证其对文件系统的使用是否符合最佳实践。
此解决方案的初始实现很简单:“基本”可以由现有“数据”fxfs 或 minfs 分区上的新子目录提供支持。 不过,要维护“前期启动”的一致定义,并构建工具来强制执行这些前期启动组件中的数据持久性模式,需要付出高昂的流程和工具成本。
Fuchsia 不打算实现更简单的文件系统来支持“基本”存储功能。如果没有不同的文件系统,那么花费资源来维护由同一实现支持的两组不同客户端几乎没有任何好处,因此未选择此解决方案。
备选方案 2:为早期可变状态编写专用客户端库
当前提案建议使用现有的成熟库来序列化可变持久状态。另一种方法是使用每种目标语言(目前为 Rust 和 Go,未来可能为 C++)编写新的客户端库。
专用客户端库可以轻松强制执行最佳实践,并且比现有的通用序列化库(如 serde)更小、更易于使用。不过,设计和实现这些新库会显著增加此提案的工程成本。现有客户端使用 stash 的目的不同,封装容器也不同,因此单个客户端库可能无法满足所有这些客户端的期望。Stash 目前没有专职人员,如果致力于设计和实现新的客户端库,可能会将迁移延迟几个季度。