UBIFS 身份验证支持¶
简介¶
UBIFS 利用 fscrypt 框架为文件内容和文件名提供机密性。这可以防止攻击者在某个时间点读取文件系统内容的攻击。一个典型的例子是丢失的智能手机,攻击者在没有文件系统解密密钥的情况下无法读取设备上存储的个人数据。
在当前状态下,UBIFS 加密并不能阻止攻击者修改文件系统内容,而用户随后使用该设备的情况。在这种情况下,攻击者可以随意修改文件系统内容而用户不会注意到。一个例子是修改二进制文件以在执行时执行恶意操作 [DMC-CBC-ATTACK]。由于 UBIFS 的大多数文件系统元数据都以明文形式存储,因此很容易交换文件并替换其内容。
其他全盘加密系统(如 dm-crypt)覆盖了所有文件系统元数据,这使得此类攻击更加复杂,但并非不可能。特别是,如果攻击者在多个时间点获得对设备的访问权限。对于 dm-crypt 和其他基于 Linux 块 IO 层的系统,可以使用 dm-integrity 或 dm-verity 子系统 [DM-INTEGRITY, DM-VERITY] 在块层获得完整的数据身份验证。这些也可以与 dm-crypt 结合使用 [CRYPTSETUP2]。
本文档描述了一种获取文件内容_和_ UBIFS 的完整元数据身份验证的方法。由于 UBIFS 使用 fscrypt 进行文件内容和文件名加密,因此身份验证系统可以与 fscrypt 绑定,以便利用诸如密钥派生之类的现有功能。但是,也可以在不使用加密的情况下使用 UBIFS 身份验证。
MTD、UBI 和 UBIFS¶
在 Linux 上,MTD(内存技术设备)子系统提供了一个统一的接口来访问原始闪存设备。在 MTD 之上工作的更重要的子系统之一是 UBI(未排序块映像)。它为闪存设备提供卷管理,因此有点类似于块设备的 LVM。此外,它还处理特定于闪存的磨损均衡和透明 I/O 错误处理。UBI 为其上层提供逻辑擦除块 (LEB),并将它们透明地映射到闪存上的物理擦除块 (PEB)。
UBIFS 是一个用于原始闪存的文件系统,它在 UBI 之上运行。因此,磨损均衡和一些闪存特定内容留给了 UBI,而 UBIFS 则专注于可伸缩性、性能和可恢复性。
+------------+ +*******+ +-----------+ +-----+
| | * UBIFS * | UBI-BLOCK | | ... |
| JFFS/JFFS2 | +*******+ +-----------+ +-----+
| | +-----------------------------+ +-----------+ +-----+
| | | UBI | | MTD-BLOCK | | ... |
+------------+ +-----------------------------+ +-----------+ +-----+
+------------------------------------------------------------------+
| MEMORY TECHNOLOGY DEVICES (MTD) |
+------------------------------------------------------------------+
+-----------------------------+ +--------------------------+ +-----+
| NAND DRIVERS | | NOR DRIVERS | | ... |
+-----------------------------+ +--------------------------+ +-----+
Figure 1: Linux kernel subsystems for dealing with raw flash
在内部,UBIFS 维护多个持久存储在闪存上的数据结构
索引:一个闪存上的 B+ 树,其中叶节点包含文件系统数据
日志:一个附加的数据结构,用于在更新闪存上的索引之前收集 FS 更改并减少闪存磨损。
树节点缓存 (TNC):一个内存中的 B+ 树,它反映了当前的 FS 状态,以避免频繁的闪存读取。它基本上是索引的内存表示,但包含额外的属性。
LEB 属性树 (LPT):一个用于每个 UBI LEB 的可用空间记帐的闪存 B+ 树。
在本节的其余部分,我们将更详细地介绍闪存上的 UBIFS 数据结构。TNC 在这里不太重要,因为它永远不会直接持久存储到闪存上。有关 UBIFS 的更多详细信息,请参见 [UBIFS-WP]。
UBIFS 索引和树节点缓存¶
基本的闪存上的 UBIFS 实体称为节点。UBIFS 知道不同类型的节点。例如,数据节点 (struct ubifs_data_node
),用于存储文件内容块,或 inode 节点 (struct ubifs_ino_node
),表示 VFS inode。几乎所有类型的节点都共享一个公共头 (ubifs_ch
),其中包含基本信息,如节点类型、节点长度、序列号等(请参阅内核源中的 fs/ubifs/ubifs-media.h
)。例外情况是 LPT 的条目和一些不太重要的节点类型,如填充节点,用于填充 LEB 末尾的不可用内容。
为了避免在每次更改时重写整个 B+ 树,它被实现为游走树,其中只重写更改的节点,并且先前版本在不立即擦除它们的情况下被废弃。因此,索引不是存储在闪存上的单个位置,而是游走在周围,并且只要包含它们的 LEB 未被 UBIFS 重用,闪存上就会有废弃的部分。为了找到索引的最新版本,UBIFS 将一个名为主节点的特殊节点存储到 UBI LEB 1 中,该节点始终指向 UBIFS 索引的最新根节点。为了实现可恢复性,主节点还会被复制到 LEB 2。因此,挂载 UBIFS 只是简单地读取 LEB 1 和 2 以获取当前主节点,然后从那里获取最新闪存索引的位置。
TNC 是闪存上索引的内存表示。它包含每个节点的某些额外的运行时属性,这些属性不会持久存储。其中之一是一个脏标志,它标记下次将索引写入闪存时必须持久存储的节点。TNC 充当写回缓存,并且对闪存上索引的所有修改都是通过 TNC 完成的。像其他缓存一样,TNC 不必将整个索引镜像到内存中,而是在需要时从闪存读取部分索引。提交是 UBIFS 更新闪存上文件系统结构(如索引)的操作。在每次提交时,标记为脏的 TNC 节点都会写入闪存,以更新持久存储的索引。
日志¶
为了避免磨损闪存,索引仅在满足某些条件时才被持久存储(提交)(例如,fsync(2)
)。日志用于记录索引提交之间发生的任何更改(以 inode 节点、数据节点等形式)。在挂载期间,日志会从闪存读取并重播到 TNC 上(它将根据需要从闪存索引创建)。
UBIFS 为日志保留了一组 LEB,称为日志区域。日志区域 LEB 的数量是在创建文件系统时配置的(使用 mkfs.ubifs
),并存储在超级块节点中。日志区域仅包含两种类型的节点:引用节点和提交开始节点。每当执行索引提交时,都会写入提交开始节点。每次更新日志时,都会写入引用节点。每个引用节点都指向此日志条目一部分的闪存上其他节点(inode 节点、数据节点等)的位置。这些节点称为芽,它们描述了实际的文件系统更改,包括它们的数据。
日志区域维护为环形。每当日志几乎满时,就会启动提交。这还会写入一个提交开始节点,以便在挂载期间,UBIFS 将寻找最新的提交开始节点,并且只重放之后的所有引用节点。提交开始节点之前的每个引用节点都将被忽略,因为它们已经是闪存上索引的一部分。
在写入日志条目时,UBIFS 首先确保有足够的空间来写入此条目的引用节点和芽。然后,写入引用节点,然后写入描述文件更改的芽。在重放时,UBIFS 将记录每个引用节点并检查引用的 LEB 的位置以发现芽。如果这些芽已损坏或丢失,UBIFS 将尝试通过重新读取 LEB 来恢复它们。但是,这仅适用于日志的最后一个引用的 LEB。只有这种情况可能会因断电而损坏。如果恢复失败,UBIFS 将无法挂载。其他任何 LEB 的错误都将直接导致 UBIFS 挂载操作失败。
| ---- LOG AREA ---- | ---------- MAIN AREA ------------ |
-----+------+-----+--------+---- ------+-----+-----+---------------
\ | | | | / / | | | \
/ CS | REF | REF | | \ \ DENT | INO | INO | /
\ | | | | / / | | | \
----+------+-----+--------+--- -------+-----+-----+----------------
| | ^ ^
| | | |
+------------------------+ |
| |
+-------------------------------+
Figure 2: UBIFS flash layout of log area with commit start nodes
(CS) and reference nodes (REF) pointing to main area
containing their buds
LEB 属性树/表¶
LEB 属性树用于存储每个 LEB 的信息。这包括 LEB 类型以及 LEB 上可用和脏(旧的、过时的内容)空间 [1] 的数量。该类型很重要,因为 UBIFS 永远不会在单个 LEB 上混合索引节点和数据节点,因此每个 LEB 都有特定的用途。这对于可用空间计算也很有用。有关更多详细信息,请参阅 [UBIFS-WP]。
LEB 属性树再次是一个 B+ 树,但它比索引小得多。由于其尺寸较小,它始终在每次提交时作为一个块写入。因此,保存 LPT 是一项原子操作。
UBIFS 身份验证¶
本章介绍 UBIFS 身份验证,它使 UBIFS 能够验证闪存上存储的元数据和文件内容的真实性和完整性。
威胁模型¶
UBIFS 身份验证可以检测离线数据修改。虽然它不能阻止它,但它允许(受信任的)代码检查闪存上的文件内容和文件系统元数据的完整性和真实性。这涵盖了文件内容被交换的攻击。
UBIFS 身份验证无法防止完整闪存内容的滚回。也就是说,攻击者仍然可以转储闪存并在稍后恢复,而不会被检测到。它也不能防止单个索引提交的部分滚回。这意味着攻击者能够部分撤消更改。这是可能的,因为 UBIFS 不会立即覆盖索引树或日志的过时版本,而是将其标记为过时,并由垃圾回收在稍后将其擦除。攻击者可以通过擦除当前树的部分并恢复仍在闪存上且尚未被擦除的旧版本来利用这一点。这是可能的,因为每次提交都会始终写入索引根节点和主节点的新版本,而不会覆盖以前的版本。UBI 的磨损均衡操作进一步帮助了这一点,它将内容从一个物理擦除块复制到另一个物理擦除块,并且不会原子地擦除第一个擦除块。
UBIFS 身份验证不涵盖攻击者在提供身份验证密钥后能够在设备上执行代码的攻击。必须采取额外的措施,如安全启动和可信启动,以确保只有受信任的代码在设备上执行。
身份验证¶
为了能够完全信任从闪存读取的数据,存储在闪存上的所有 UBIFS 数据结构都经过身份验证。即
索引,包括文件内容、文件元数据(如扩展属性、文件长度等)。
日志,它还包含文件内容和元数据,通过记录文件系统的更改。
LPT,它存储 UBI LEB 元数据,UBIFS 使用该元数据进行空闲空间计算。
索引身份验证¶
通过 UBIFS 的“漫游树”概念,它已经处理了仅更新和持久化从叶节点到整个 B+ 树的根节点的已更改部分。这使我们能够使用每个节点的子节点的哈希来增强树的索引节点。因此,索引基本上也是一棵 Merkle 树。由于索引的叶节点包含实际的文件系统数据,因此它们的父索引节点的哈希覆盖了所有文件内容和文件元数据。当文件更改时,UBIFS 索引会相应地从叶节点到根节点(包括主节点)进行更新。可以挂钩此过程以同时仅为每个更改的节点重新计算哈希。每当读取文件时,UBIFS 都可以验证从每个叶节点到根节点的哈希,以确保节点的完整性。
为了确保整个索引的真实性,UBIFS 主节点存储了其自身内容和索引树根节点的哈希的密钥哈希(HMAC)。如上所述,每当索引被持久化时(即在索引提交时),主节点始终会被写入闪存。
使用这种方法,仅更改 UBIFS 索引节点和主节点以包含哈希。所有其他类型的节点将保持不变。这减少了对于 UBIFS 用户(即嵌入式设备)来说非常宝贵的存储开销。
+---------------+
| Master Node |
| (hash) |
+---------------+
|
v
+-------------------+
| Index Node #1 |
| |
| branch0 branchn |
| (hash) (hash) |
+-------------------+
| ... | (fanout: 8)
| |
+-------+ +------+
| |
v v
+-------------------+ +-------------------+
| Index Node #2 | | Index Node #3 |
| | | |
| branch0 branchn | | branch0 branchn |
| (hash) (hash) | | (hash) (hash) |
+-------------------+ +-------------------+
| ... | ... |
v v v
+-----------+ +----------+ +-----------+
| Data Node | | INO Node | | DENT Node |
+-----------+ +----------+ +-----------+
Figure 3: Coverage areas of index node hash and master node HMAC
对于健壮性和断电安全最重要的是原子地持久化哈希和文件内容。这里,现有 UBIFS 逻辑中如何持久化已更改节点的设计已经为此目的而设计,以便在持久化期间发生断电时,UBIFS 可以安全地恢复。向索引节点添加哈希不会改变这一点,因为每个哈希都将与其各自的节点原子地持久化。
日志身份验证¶
日志也经过身份验证。由于日志是连续写入的,因此有必要也频繁地向日志添加身份验证信息,以便在发生断电时,不会有太多数据无法进行身份验证。这是通过从提交开始节点开始,对先前的引用节点、当前引用节点和芽节点创建连续哈希来实现的。有时,只要合适,就在芽节点之间添加身份验证节点。这种新节点类型包含当前哈希链状态的 HMAC。这样,就可以对日志进行身份验证,直到最后一个身份验证节点。日志的尾部可能没有身份验证节点,因此无法进行身份验证,并且在日志重放期间会跳过它。
这是我们获得的日志身份验证的图片
,,,,,,,,
,......,...........................................
,. CS , hash1.----. hash2.----.
,. | , . |hmac . |hmac
,. v , . v . v
,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ...
,..|...,...........................................
, | ,
, | ,,,,,,,,,,,,,,,
. | hash3,----.
, | , |hmac
, v , v
, REF#1 -> bud -> bud,-> auth ...
,,,|,,,,,,,,,,,,,,,,,,
v
REF#2 -> ...
|
V
...
由于哈希还包括引用节点,因此攻击者无法重新排序或跳过任何日志头以进行重放。攻击者只能从日志末尾删除芽节点或引用节点,从而有效地将文件系统最多回滚到上次提交。
日志区域的位置存储在主节点中。由于如上所述,主节点使用 HMAC 进行身份验证,因此无法在不被检测到的情况下篡改它。日志区域的大小在使用 mkfs.ubifs 创建文件系统时指定,并存储在超级块节点中。为了避免篡改此处存储的此值和其他值,将 HMAC 添加到超级块结构中。超级块节点存储在 LEB 0 中,并且仅在功能标志或类似更改时修改,而永远不会在文件更改时修改。
LPT 身份验证¶
闪存上 LPT 根节点的位置存储在 UBIFS 主节点中。由于 LPT 在每次提交时都会以原子方式写入和读取,因此无需对树的各个节点进行身份验证。通过存储在主节点中的简单哈希来保护完整 LPT 的完整性就足够了。由于主节点本身经过身份验证,因此可以通过验证主节点的真实性并将此处存储的 LTP 哈希与从读取的片上 LPT 计算出的哈希进行比较来验证 LPT 的真实性。
密钥管理¶
为了简单起见,UBIFS 身份验证使用单个密钥来计算超级块、主节点、提交开始节点和引用节点的 HMAC。必须在创建文件系统 (mkfs.ubifs) 时提供此密钥,以对超级块节点进行身份验证。此外,必须在挂载文件系统时提供此密钥,以验证经过身份验证的节点并为更改生成新的 HMAC。
UBIFS 身份验证旨在与 UBIFS 加密 (fscrypt) 并行运行,以提供机密性和真实性。由于 UBIFS 加密对每个目录采用不同的加密策略,因此可以有多个 fscrypt 主密钥,并且可能存在没有加密的文件夹。另一方面,UBIFS 身份验证采用“全有或全无”的方法,即它要么对文件系统的所有内容进行身份验证,要么不对任何内容进行身份验证。因此,并且由于 UBIFS 身份验证也应在没有加密的情况下使用,因此它不与 fscrypt 共享同一个主密钥,而是管理专用的身份验证密钥。
提供身份验证密钥的 API 尚未定义,但可以通过类似当前在 fscrypt 中使用的方式通过用户空间经由密钥环来提供密钥。但是应该注意的是,当前的 fscrypt 方法已经显示出其缺陷,并且用户空间 API 最终会更改 [FSCRYPT-POLICY2]。
但是,用户可以在用户空间中提供一个涵盖 UBIFS 身份验证和加密的单个密码或密钥。这可以通过相应的用户空间工具来解决,该工具除了用于加密的派生 fscrypt 主密钥之外,还为身份验证派生第二个密钥。
为了能够在挂载时检查是否提供了正确的密钥,UBIFS 超级块节点还将存储身份验证密钥的哈希。此方法类似于为 fscrypt 加密策略 v2 提出的方法 [FSCRYPT-POLICY2]。
未来扩展¶
在某些情况下,供应商希望向客户提供经过身份验证的文件系统映像,则应有可能在不共享秘密 UBIFS 身份验证密钥的情况下进行此操作。相反,除了每个 HMAC 之外,还可以存储数字签名,其中供应商与文件系统映像一起共享公钥。如果此文件系统必须在之后进行修改,则 UBIFS 可以在首次挂载时将所有数字签名与 HMAC 交换,类似于 IMA/EVM 子系统处理这种情况的方式。然后,必须以正常方式预先提供 HMAC 密钥。
参考文献¶
[CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html
[DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-encrypted-luks-partitions/
[DM-INTEGRITY] https://linuxkernel.org.cn/doc/Documentation/device-mapper/dm-integrity.rst
[DM-VERITY] https://linuxkernel.org.cn/doc/Documentation/device-mapper/verity.rst
[FSCRYPT-POLICY2] https://www.spinics.net/lists/linux-ext4/msg58710.html
[UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf