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