GLEP 74:使用清单文件进行全树验证
作者 | Michał Górny <[email protected]>,Robin Hugh Johnson <[email protected]>,Ulrich Müller <[email protected]> |
---|---|
类型 | 标准轨道 |
状态 | 最终 |
版本 | 1.3 |
创建日期 | 2017-10-21 |
最后修改日期 | 2022-10-30 |
发布历史 | 2017-10-26, 2017-11-16, 2018-02-08, 2022-09-08, 2022-09-11, 2022-10-22 |
要求 | 59 61 |
替换 | 44 58 60 |
GLEP 源代码 | glep-0074.rst |
摘要
本 GLEP 扩展了清单文件格式,以涵盖全树文件完整性和真实性检查。该格式旨在面向未来,高效且提供向后兼容性的方法。
变更
- v1.3
- 正式指定了当前支持的散列算法和压缩清单格式集。
- v1.2
- 指定了用于清单的换行符约定。
- v1.1
- 删除了清单树覆盖的所有文件必须位于同一文件系统上的限制。
动机
GLEP 44 [1] 中定义的清单文件提供了当前用于验证 Gentoo 中 distfile 和包文件完整性的方法。结合 OpenPGP 签名,它们提供了确保覆盖文件真实性的方法。但是,正如 GLEP 57 [2] 中所指出的,它们缺乏提供全树真实性验证的能力,因为它们不涵盖包目录之外的任何文件。特别是,它们为第三方提供了多种将恶意代码注入 ebuild 环境的方法。
从历史上看,关于为整个存储库提供真实性覆盖率的主题已经多次提及。最值得注意的努力是 Robin H. Johnson 在 2008 年提出的 GLEP 58 [3] 和 60 [5]。它们在 2010 年被理事会接受,但从未实施。当潜在的实施工作在 2017 年开始时,关于规范的新讨论出现了。这促使创建了一个竞争 GLEP,该 GLEP 将提供对旧 GLEP 的重新设计的替代方案。
本规范的设计目标如下
- 它应该提供确保整个存储库真实性的方法,包括防止注入额外的文件。
- 该格式应该足够通用,既适用于 Gentoo 存储库,也适用于具有不同特征的第三方存储库。
- 清单文件应该能够独立验证,即无需了解底层存储库格式的任何详细信息。
规范
清单文件格式
本规范重用并扩展了 GLEP 44 [1] 中定义的清单文件格式。出于其目的,文件类型字段被重新用作通用标签,也可以指示附加的(非校验和)元数据。适当地,这些标签可以后跟其他用空格分隔的值。
清单文件是一个面向行的文本文件。每一行包含一个清单条目,并且由一个或多个用单个空格字符(U+0020)分隔的字段组成。标签及其对应的字段在现代清单标签和已弃用的清单标签部分中定义。
除非另有说明,否则清单文件中使用的路径相对于包含清单文件的目录。路径不得引用父目录(..)。正斜杠(/)用作路径组件分隔符。
清单文件使用 UTF-8 编码。换行符(U+000A)用于分隔行。为了最佳兼容性,空行和任何其他空格,包括回车符(U+000D)应该被实现忽略。
清单文件位置和嵌套
位于存储库根目录中的清单文件称为顶级清单,它用于执行全树验证。为了验证真实性,它必须使用 OpenPGP 签名,使用 RFC 4880 § 7 中定义的装甲明文格式或后续标准 [7]。
顶级清单可以引用包含在存储库子目录中的子清单。子清单传统上命名为清单;但是,实现必须支持任意名称,包括为单个目录使用多个(拆分)清单的可能性。子清单只能覆盖它所在的目录树内的文件。
子清单也可以使用 OpenPGP 装甲明文格式签名。但是,可以省略签名验证,因为它已经被签名的顶级清单覆盖。
目录树覆盖范围
该规范提供了三种跳过特定文件和目录(递归)的清单验证的方法
- 显式IGNORE清单文件中的条目,
- 通过包管理器配置注入忽略路径,
- 使用以点(.)开头的名称,这些名称始终被跳过。
顶级清单被隐式跳过,在清单文件中列出它是一个错误。所有其余未被忽略的文件必须至少被一个清单覆盖。
如果且仅当条目具有相同的语义,指定相同的大小并且两个条目共有的校验和匹配时,单个文件可以被多个相同或等效的清单条目匹配。对于单个文件,如果被具有不同语义、文件大小或校验和值的多个条目匹配,则这是一个错误。对于匹配IGNORE或位于被忽略目录内的文件,指定另一个条目是一个错误。
文件条目(除了IGNORE)只能为常规文件指定。在打开文件和遍历目录时会跟踪符号链接。为不同的文件类型指定条目是一个错误。如果树包含其他类型的文件,这些文件没有被忽略,则需要通过显式IGNORE.
路径和文件名编码
清单文件中的路径字段必须包含 Unicode 字符,不包括反斜杠(\)以及在当前版本的 Unicode 标准 [8] 中归类为控制字符或空格的字符。
实现可以选择支持扩展文件名编码,以支持这些路径。如果不支持编码,则实现必须拒绝包含任何使用不符合规范名称的文件的目录,以及文件名字段包含此类文件名的清单文件。
如果支持编码,则路径中存在的任何排除字符必须使用以下转义序列之一进行编码
- 中的字符U+0000到U+007F范围可以编码为\xHH其中HH指定零填充的十六进制字符代码,
- 中的字符U+0000到U+FFFF范围可以编码为\uHHHH其中HHHH指定零填充的十六进制字符代码,
- 中的字符\UHHHHHHHH其中HHHHHHHH指定零填充的十六进制字符代码。
反斜杠在任何其他上下文中使用都是无效的,并且文件名中存在反斜杠必须进行编码。用作路径组件分隔符的反斜杠应该替换为正斜杠。
编码也可以用于其他字符。特别是,转义不可打印字符可能是可取的。
大小和校验和字段
用于描述文件的清单条目列出了文件的大小(以字节为单位)和一个或多个校验和。大小表示为无符号十进制整数。校验和使用字段对表示,每对字段中的第一个字段指定散列名称,第二个字段指定其值。散列的名称及其值的编码在校验和算法部分中指定。
指定没有值的散列名称是无效的。
文件验证
当根据清单验证文件时,使用以下规则
- 如果文件被直接或间接地由IGNORE类型的条目覆盖,则验证始终成功。
- 如果文件被MANIFEST, DATA,
MISC, EBUILD或AUX类型的条目覆盖
- 如果文件不存在,则验证失败,
- 如果文件存在但大小不同或其中一个校验和不匹配,则验证失败,
- 否则,验证成功。
- 如果文件存在但未在清单中列出,则验证失败。
除非另有说明,否则包管理器不得允许使用任何验证失败的文件。如果包管理器可能引用验证失败的文件,它可能会拒绝任何包甚至整个存储库。
时间戳验证
顶级清单文件可以包含TIMESTAMP用于记录针对树更新分发攻击的条目。如果存在此类条目,则应在至少一个清单发生更改时更新。每个唯一的时间戳值必须对应于单个树状态。
在验证过程中,客户端应将时间戳与从本地时钟或可信时间源获得的更新时间进行比较。如果比较结果表明接收时清单已过时,客户端应失败验证或要求用户手动确认。
此外,清单提供者可以使用其他方法,通过可信来源的安全通道分发最近生成的清单的时间戳,以便进行精确比较。此类解决方案的具体细节不在本规范范围之内。
TIMESTAMP条目也可能存在于子清单中。这些时间戳不得晚于顶级清单的时间戳(如果存在)。本规范没有定义它们的任何特定用途。
现代清单标签
清单文件可以指定以下标签
- TIMESTAMP <iso8601>
- 指定清单文件上次更新的时间戳。时间戳必须是有效的秒精度 RFC 3339 格式的组合日期和时间,以 UTC 时区表示 [9],即使用以下strftime()格式字符串%Y-%m-%dT%H:%M:%SZ. 可选。包管理器可以使用它来检测过时的存储库检出,如 时间戳验证 中所述。
- MANIFEST <path> <size> <checksums>...
- 指定子清单。子清单必须像普通文件一样进行验证。如果验证成功,则子清单中的条目将被包含在验证中,如 清单文件位置和嵌套 中所述。
- IGNORE <path>
- 忽略清单检查中的子目录或文件。如果指定路径存在,它及其内容将从清单验证中省略(始终通过)。路径必须是普通文件或目录路径,没有尾部斜杠。通配符不受支持,通配符字符按字面解释。
- DATA <path> <size> <checksums>...
- 指定受清单验证的普通文件。该文件需要通过验证。用于所有与任何其他类型不匹配的文件。
- DIST <filename> <size> <checksums>...
指定作为以下部分获取的文件验证所用的发行版文件条目SRC_URI. 文件名必须与 PMS [10] 中指定的用于存储获取文件的名称匹配。如果获取的文件验证失败,包管理器必须拒绝它。DIST条目适用于指定它们的清单文件下方的所有包。
此条目特定于包管理器使用,在验证本地目录时未使用。
已弃用的清单标签
为了向后兼容,以下标签还允许在包目录级别使用
- EBUILD <filename> <size> <checksums>...
- 等同于DATA类型。
- MISC <path> <size> <checksums>...
- 等同于DATA类型。历史上表明,如果包管理器在非严格模式下运行,则可以忽略验证失败。但是,这种行为已被弃用。
- AUX <filename> <size> <checksums>...
- 等同于DATA类型,但文件名相对于files/子目录。
全树验证算法
为了执行全树验证,可以使用以下算法
- 将存储库中存在的所有文件收集到present集合中。
- 从顶级清单文件开始。验证其 OpenPGP 签名。可以选择验证TIMESTAMP条目(如果存在),如时间戳验证中所述。从present集合中删除顶级清单。
- 处理所有MANIFEST条目,递归。根据 文件验证 部分验证清单文件,并将它们的条目包含在当前清单条目列表中(使用相对于包含清单的目录的路径)。
- 处理所有IGNORE条目。从present集合中删除与它们匹配的任何路径。
- 将所有由DATA, MISC, EBUILD和AUX条目覆盖的文件收集到covered集合中。
- 根据 清单文件位置和嵌套 中的说明,验证covered集合中的条目是否存在不兼容的重复项以及与忽略文件的冲突。
- 根据 文件验证 部分,验证present和covered集合并集中的所有文件。
查找父清单的算法
为了从当前目录找到顶级清单,可以使用以下算法
- 将当前目录存储为original,
- 如果当前目录包含一个清单文件
- 如果一个IGNORE条目在清单文件中覆盖了original目录(或其父目录之一),停止。
- 否则,将当前目录存储为last_found。
- 如果当前目录是根系统目录(/),停止。
- 否则,进入父目录并跳转到步骤 2。
算法停止后,last_found将包含相关的顶级清单。如果last_found为空,则目录树不包含任何有效的顶级清单候选项,应该在original目录中创建一个。
找到顶级清单后,其MANIFEST条目应用于查找顶级清单下方的任何子清单,直到并包括original目录。请注意,这些子清单可以使用与清单.
校验和算法
名称 | 规范 | 位数 | 编码 | 备注 |
---|---|---|---|---|
BLAKE2B | RFC 7693 [12] | 512 | 十六进制 | 推荐 |
BLAKE2S | 256 | 十六进制 | ||
MD5 | RFC 1321 [13] | 128 | 十六进制 | 已弃用 |
RMD160 | RIPEMD-160 [14] | 160 | 十六进制 | |
SHA1 | FIPS 180-4 [15] | 160 | 十六进制 | 已弃用 |
SHA256 | 256 | 十六进制 | ||
SHA512 | 512 | 十六进制 | 推荐 | |
SHA3_256 | FIPS 202 [16] | 256 | 十六进制 | |
SHA3_512 | 512 | 十六进制 | ||
STREEBOG256 | RFC 6986 [17] | 256 | 十六进制 | |
STREEBOG512 | 512 | 十六进制 | ||
WHIRLPOOL | Whirlpool [18] | 512 | 十六进制 |
使用以下哈希值编码
- 十六进制
- 以无符号十六进制整数表示的哈希值,使用数字0到9和小写字母a到f,没有前缀或后缀。
任何新的哈希必须在清单文件中使用之前添加到本规范中。添加新的哈希被认为是对 GLEP 的向后兼容更改。建议新的哈希以 Pythonhashlib模块算法名称命名,转换为大写,并将破折号替换为下划线。
实现可以实现所列哈希的任意子集。为了实现最佳互操作性,它应该至少实现推荐的哈希。如果实现了已弃用的哈希,最好默认情况下不允许使用它们。
如果一个条目使用不同的算法指定多个哈希,实现可以选择验证其中任意一个子集。但是,如果任何测试的哈希产生不匹配,则验证必须失败。
如果特定哈希不受支持或未知,实现可以忽略它或报告失败。但是,必须支持特定条目中指定的至少一种算法才能使验证成功。
清单压缩
清单文件压缩主题由 GLEP 61 [6] 涵盖。本节仅讨论清单压缩与本规范之间的互操作性问题。
压缩的清单文件需要为其压缩算法添加后缀。此后缀应用于识别压缩并透明地解压缩清单。支持的格式在 压缩文件格式 部分中指定。
顶级清单文件不能被压缩。由于 OpenPGP 签名覆盖未压缩的文本并自行压缩,因此数据将不得不先不进行任何验证就解压缩。这可能会让用户暴露于例如 zip 炸弹或解压缩漏洞的攻击。
只要本规范提到子清单,它们可以使用任何名称,但也需要使用特定的压缩后缀。该MANIFEST条目需要指定完整名称,包括压缩后缀,并且验证是在压缩文件上执行的。
本规范允许未压缩的清单与它们的压缩副本共存,并且允许多种压缩格式共存。如果是这种情况,这些文件必须具有相同的未压缩内容,并且本规范可以自由地使用相同基本名称中的任一文件。
压缩文件格式
工具名称 | 后缀 | 规范 | 备注 |
---|---|---|---|
bzip2 | .bz2 | (未知) | |
gzip | .gz | RFC 1952 [19] | 推荐 |
lz4 | .lz4 | (未知) | |
lzip | .lz | RFC 草案 [20] | |
lzma | .lzma | (未知) | 已弃用 |
lzop | .lzo | (未知) | |
xz | .xz | xz [21] | |
zstd | .zst | RFC 8878 [22] |
任何新的格式必须在清单文件中使用之前添加到本规范中。添加新的压缩文件格式被认为是对 GLEP 的向后兼容更改。建议新的格式使用其参考(最常见)文件后缀。
实现可以实现所列格式的任意子集。为了实现最佳互操作性,它应该至少实现推荐的格式。应避免使用已弃用的格式。
如果多个清单变体使用不同的压缩文件格式共存,实现可以选择使用其中任意一个子集。但是,所有这些变体都必须针对包含的清单中存储的哈希进行验证。如果它们被解压缩,则结果内容必须相同。
如果压缩文件格式不受支持,并且存在使用支持格式的变体,则应使用其他变体。但是,必须至少存在一个支持的变体才能使验证成功。
组合多个清单树(信息性)
本规范允许嵌套多个分层清单树。在此布局中,清单树的特定目录可以作为另一个顶级清单的一部分以及独立的清单树(在没有父目录的情况下获取时)进行验证。
为了使此工作正常,目录中的子清单文件还必须满足顶级清单文件的要求。也就是说
- 它必须命名为清单并且不被压缩,
- 它必须覆盖此目录及其子目录中的所有文件(即目录树中的任何文件都不能被父清单覆盖),
- 如果需要身份验证,则必须使用 OpenPGP 签名。
应该注意的是,如果此类目录是有效清单树的子目录,则子清单需要根据顶级清单有效,并且 OpenPGP 签名将被忽略,如 清单文件位置和嵌套 中所述。只有在没有父目录的情况下获取该目录时,才会表现出顶级行为。
包管理器集成(信息性)
支持全树清单验证的包管理器应在通过 rsync 使用 Gentoo 存储库时默认启用它,并要求影响其操作的每个位置在使用之前成功验证。
只有用户可以明确地禁用全树验证(例如,使用配置文件)。出于安全原因,包管理器绝不应尝试根据存储库中的任何数据来禁用它。特别是,通过metadata/layout.conf或者基于顶级 Manifest 的存在,因为它允许恶意第三方轻松绕过验证。
此外,在针对 Manifest 文件进行验证之前,存储库中存在的任何文件都无法处理。 这包括metadata/layout.conf和profiles/repo_name文件。 如果顶级 Manifest 不存在或这些文件未通过验证,则启用完整树验证的包管理器必须立即拒绝存储库。
清单文件示例(信息性)
Gentoo 存储库的顶级 Manifest 文件示例具有以下内容
TIMESTAMP 2017-10-30T10:11:12Z IGNORE distfiles IGNORE local IGNORE lost+found IGNORE packages MANIFEST app-accessibility/Manifest 14821 SHA256 1b5f.. SHA512 f7eb.. ... MANIFEST eclass/Manifest.gz 50812 SHA256 8c55.. SHA512 2915.. ...
软件包目录的现代 Manifest 示例(忽略向后兼容性)具有以下内容
DATA SphinxTrain-0.9.1-r1.ebuild 932 SHA256 3d3b.. SHA512 be4d.. DATA SphinxTrain-1.0.8.ebuild 912 SHA256 f681.. SHA512 0749.. DATA metadata.xml 664 SHA256 97c6.. SHA512 1175.. DATA files/gcc.patch 816 SHA256 b56e.. SHA512 2468.. DATA files/gcc34.patch 333 SHA256 c107.. SHA512 9919.. DIST SphinxTrain-0.9.1-beta.tar.gz 469617 SHA256 c1a4.. SHA512 1b33.. DIST sphinxtrain-1.0.8.tar.gz 8925803 SHA256 548e.. SHA512 465d..
安全注意事项(信息性)
Manifest 文件是文本文件,作为更大的文件集的一部分进行传输,以便为其他文件提供完整性和真实性验证。 它们主要用于在本地处理以验证传输的文件。 它们通常与 rsync 协议一起使用,并在 tar 存档中使用。
该格式不支持可执行内容,也不支持发出网络请求。 它的安全性主要是在打开和读取本地文件以计算哈希值的上下文中考虑的。
根据交付方法,可能在经过验证的文件集中包含特殊文件和符号链接。 尝试读取特殊文件(例如命名管道或设备,例如/dev/urandom)可能会导致工具挂起或进入无限循环。 规范明确要求实现验证文件类型并拒绝处理非规则文件。
使用符号链接允许计算任意路径的校验和,包括可能包含敏感内容的文件和特殊文件系统上的文件,例如/proc文件系统。 读取这些文件不构成立即风险,也不显示校验和不匹配到本地风险。 但是,如果用户报告校验和失败,存在暴露敏感信息的风险。 实现可以采取措施来降低风险,例如,通过最小化报告校验和不匹配的信息量并警告符号链接。
基本原理
独立格式
在继续设计之前需要提出的第一个问题是 Manifest 文件格式应该是独立的,还是与存储库格式紧密绑定。
独立格式已被选中,因为它具有以下三个优点
- 它更具未来性。 如果对存储库格式进行了不兼容的更改,则只有开发人员需要升级他们用来生成 Manifest 的工具。 用于验证更新的 Manifest 的工具将继续工作。
- 它更灵活、更通用。 使用专用工具,Manifest 文件可用于签署和验证任意文件集。
- 它使验证工具更简单。 特别是,我们可以轻松编写一个独立的验证工具,该工具可以在任何发行版上运行,而无需依赖包管理器实现或重写其部分内容。
设计独立格式要求 Manifest 携带足够的信息来执行验证,遵循 Gentoo 存储库特有的所有规则。
换行符约定
在 1.2 版之前,规范没有指明用于换行符的编码。 由于该格式主要用于 Gentoo Linux 系统,因此已更改为遵循 Unix 约定,使用换行符。 但是,为了实现最佳互操作性,实现应准备将多余的回车符视为空白并忽略它们。
树设计
设计的第二个要点是确定 Manifest 文件应该是分层结构的,还是独立的。 这两种选择都有各自的优势。
在分层模型中,每个子 Manifest 文件都由更高级别的 Manifest 覆盖。 因此,只有顶级 Manifest 需要使用 OpenPGP 签名,后续的 Manifest 仅需通过存储在父 Manifest 中的校验和进行验证。 这具有以下含义
- 验证存储库中的任何文件集都需要使用最相关的 Manifest 和父 Manifest 的校验和。
- 顶级 Manifest 的 OpenPGP 签名只需要在每个进程中验证一次。
- 更改任何文件集都需要更新相关的 Manifest 及其父 Manifest,直到顶级 Manifest,并签署最后一个 Manifest。
- 因此,顶级 Manifest 在每次提交时都会更改,并且各种中间级 Manifest 会经常更改(并且需要传输)。
在独立模型中,每个子 Manifest 文件独立于父 Manifest。 因此,它们中的每一个都需要独立签署和验证。 但是,父 Manifest 仍然需要列出子 Manifest(尽管没有验证数据),以便检测子目录的删除或替换。 这具有以下含义
- 验证存储库中的任何文件集都需要使用校验和并验证最相关的 Manifest 文件的签名。
- 更改任何文件集都需要更新相关的 Manifest 并重新签署它们。
- 父 Manifest 仅在子目录中添加或删除 Manifest 时更新。 因此,它们很少更改。
虽然两种模型都有各自的优势,但分层模型已被选中,因为它将 OpenPGP 操作(这些操作比较昂贵)的数量减少到最低限度。
树布局限制
该算法旨在主要与 ebuild 存储库一起使用,这些存储库通常只包含文件和目录。 目录不提供任何有用的元数据用于验证,并且为其他文件类型指定特殊条目毫无意义。 因此,规范仅限于处理常规文件。
Gentoo 存储库不使用符号链接。 但是,一些 Gentoo 存储库确实使用符号链接。 为了提供一个简单的解决方案来处理符号链接,而无需费心实现对它们的特殊处理,使用的是隐式解析它们的常见行为。 因此,指向文件的符号链接被存储为常规文件,指向目录的符号链接被跟随为常规目录。
点文件被隐式忽略,因为这是为 POSIX 系统编写的软件中使用的常见概念。 所有其他文件名都需要显式IGNORE行。
提供了一种注入附加忽略条目的功能,以说明影响存储库树的站点配置 - 在其中放置附加文件,跳过一些同步类别。 此配置可以超出本 GLEP 的限制,例如,通过允许通配符或正则表达式。
跨文件系统清单
此规范的第一个版本有一个额外的要求,即 Manifest 树覆盖的所有文件必须驻留在单个文件系统上。 此要求已在 1.1 版中删除,原因在本节中概述。
最初的理由指出,此限制旨在防止在顶级 Manifest 查找算法中跨越文件系统边界。 虽然这在当时似乎是个好主意,但实际上没有理由阻止这样做,而这种特定方法仅在将文件放置在专用文件系统中时才正确地起作用。
更糟糕的是,最初的理由没有预料到 overlayfs 的使用,overlayfs 组合了多个文件系统,同时保留了它们原始的元数据,包括设备和 inode 编号。 因此,如果存储库签出到 overlayfs,则不同的文件很可能具有不同的设备编号,而 Manifest 检查由于跨越文件系统边界而失败。
由于没有针对此问题明确的解决方案,也没有充分的理由拒绝使用 overlayfs,因此取消了该限制。
这唯一的潜在缺点是实现现在可能会跟随恶意放置的指向树外部的符号链接。 如果一个普通文件被这样的符号链接替换,用户可能会被诱骗报告验证失败,报告中包含目标文件的校验和。 但是,为了发生这种情况,客户端必须使用 rsync 和--links选项,但没有--safe-links选项,这既不是 rsync 的默认行为,也不是 Portage 使用的默认配置。
文件名字符集限制
Gentoo 存储库的有效文件名字符集受 devmanual 的“文件命名规则”部分 [11] 限制,并通过 git 钩子强制执行。 有效的 distfile 名称没有明确限制 - 但是,PMS 依赖项规范语法 [10] 隐式地使使用包含空格的文件名变得不可能。
此规范旨在避免任意限制。 因此,文件名字符仅通过排除三个技术上存在问题的组来限制
- 反斜杠字符 (\)在 Windows 系统上用作路径分隔符,因此它极不可能在真实文件名中使用。 因此,它被用来实现字符编码,最小限度地降低破坏向后兼容性的风险。
- 控制字符可能会在各种程序中触发特殊行为,并使它们无法识别文本文件。 特别是,空字符 (U+0000)通常用于表示以空字符结尾的字符串的结尾。 因此,它的使用可能会破坏用 C 语言编写的实现。 其他控制字符可能会触发各种格式化例程,使文本输出混乱。
- 空白字符用于分隔 Manifest 字段和条目。 从技术上讲,限制空格 (U+0020)字符(通常用作分隔符)和换行符 (U+000A)字符(用于分隔行)就足够了,但所有空白字符都被禁止,以避免混淆和实现错误。
从历史上看,Portage 试图通过尝试找到大小字段并将之前的所有内容视为文件名来克服空格限制。 这非常脆弱,即使它有效,也只会部分解决问题。
为了保持与当前实现的兼容性,并且考虑到所有列出的字符在可预见的 Gentoo 使用中都不允许,扩展编码支持是可选的。 如果没有提供这种支持,实现必须无条件拒绝任何此类文件。 隐式忽略它们会导致混淆,并且不可能在显式IGNORE条目中使用它们。
字符编码方法提供了一种方法来克服字符限制,将工具的使用范围扩展到超出直接的 Gentoo 使用。 基于 Python unicode 字符串的反斜杠转义形式被使用,因为它可以对 Unicode 范围内的所有字符进行编码,语法对许多程序员来说很熟悉,而且反斜杠字符极不可能出现在真实文件名中。
语法仅限于实现编码所需的最小限度。 省略简写形式(例如\t或\\),以避免不必要的复杂性,并降低 shell 用户使用反斜杠直接转义空格的风险。 该\x表单限于\x00..\x7F范围,以避免更高值的歧义,这些值可能被解释为 UCS-2 代码点或 UTF-8 编码字符的一部分。
编码直接存储 UCS-2/UCS-4 字符,而不是十六进制编码的 UTF-8 字符串,以简化实现。 特别是,它使能够将 Manifest 文件作为 UTF-8 编码的文本进行处理,而无需对转义数据执行额外的 UTF-8 解码(和验证)。
URL 编码被认为是一种替代方案。 但是,它可能会与DIST条目冲突,这些条目隐式地以 URL 文件名部分命名,其中 URL 编码非常常见。
文件验证模型
验证模型旨在提供针对不同形式攻击的全面覆盖。 特别是,考虑了三种不同的操纵形式
- 更改文件内容。
- 删除文件。
- 添加新文件。
为了防止这三种情况,系统要求存储库中的所有文件都列在 Manifest 中并针对它们进行验证。
作为特例,允许忽略那些不是存储库的一部分但传统上放置在存储库中的目录。 这些目录是distfiles, local和packages. 它也可以用来忽略像CVS.
非严格清单验证
这样的 VCS 目录。MISC最初 Manifest2 格式提供了一个特殊的标签,用于和metadata.xmlChangeLog
文件。这个标签表明,除非包管理器在严格模式下工作,否则可以忽略这些文件的 Manifest 验证失败。
该规范的第一个版本继续使用这个标签。但是,经过长时间的讨论,最终决定弃用该标签以及非严格模式,并要求所有文件严格匹配。MISC类型的条目覆盖
- 提到了两个论点来证明
- 能够通过剥离不必要的文件来减小检出大小,以及
能够在本地更新自动生成的文件,而不会导致不必要的验证失败。MISC然而,在两种情况下
的用处都是值得怀疑的。标签,用于剥离不必要文件的情况主要集中在节省空间。出于此目的,剥离MISC以及类似文件几乎没有价值。用户更常见的是剥离整个包或类别。该MISC类型不适合这种情况,因此需要开发专门的包管理器机制。相同的机制也可以处理历史上使用该
类型的文件。例如,包管理器可以选择使用单个源列表生成 rsync 排除列表和 Manifest 忽略列表。自动生成文件的案例包括像use.local.desc这样的缓存文件。但是,由于安全问题,我们不能包括md5-cacheMISC在那里,这会导致缓存处理不一致。此外,这些工具在历史上被修改为提供稳定的输出,这意味着它们的内容不能在非MISC.
内容首先改变的情况下改变。这实际上违背了使用
时间戳字段
的目的。TIMESTAMP最后,非严格模式可以作为一种攻击手段。允许缺少或修改的文档文件可以用来传播错误信息,导致用户做出错误的决定。一个修改过的文件也可以用来,例如,利用 XML 解析器的漏洞。
顶级 Manifest 可选地允许使用
标签在 Manifest 中包含生成时间戳。类似的功能最初在 GLEP 58 [3] 中提出。
一个恶意第三方可能会使用排除或重放原则 [23] 来拒绝向客户端更新,同时记录客户端身份以进行攻击。时间戳字段可用于检测这种情况。为了提供更完整的保护,Gentoo 基础设施应该提供一种能力,能够从受信任的来源获取最近一段时间内所有 Manifest 的时间戳,并通过安全通道进行比较。严格来说,这些信息是由各种
metadata/timestamp*IGNORE文件提供的,这些文件已经存在。但是,将值包含在 Manifest 本身中成本很低,并提供独立执行验证的能力。
此外,一些时间戳文件是在分发过程的后期添加的,超过了 Manifest 生成阶段。这些文件很可能收到
新标签与已弃用标签
条目,因此不安全使用。DATA类型。
位于存储库根目录中的DIST该规范允许在子 Manifest 文件中使用附加时间戳以供本地使用。通用的测试工具应该忽略它们。
位于存储库根目录中的EBUILD在 Manifest2 定义的四种类型中,只有一种被重用,其余三种被单个通用EBUILD标签替换,因为该规范没有改变有关 distfile 处理的任何内容。DATA标签可以潜在地被重用于通用文件验证数据。但是,如果所有不同的数据文件都标记为
位于存储库根目录中的MISC,则会造成混淆。因此,引入了一个等效的
位于存储库根目录中的AUX类型来代替。DATA标签以及相关的非严格模式已被删除,因为它们几乎没有价值,如 非严格 Manifest 验证 部分所述。files/标签已弃用,因为它与
查找顶级清单
冗余,并且具有隐式
路径前缀的限制属性。开发此 GLEP 的参考实现带来了以下问题:当 Manifest 工具在存储库的子目录中运行时,如何找到所有相关的 Manifest?其中一个选择是通过
PARENT清单标签提供双向链接的 Manifest。但是,当创建新的 Manifest 文件时,这并不能解决问题。
相反,提出了一种遍历父目录的算法。由于没有强制性的顶级 Manifest 的显式指示器,因此该算法假设顶级 Manifest 是目录层次结构中能够覆盖当前目录的最高IGNORE。这通常是有道理的,因为 Manifest 文件需要为所有子目录提供覆盖范围,因此从该文件开始的所有 Manifest 都需要更新。
如果独立的 Manifest 树嵌套在目录结构中,则需要使用清单条目将它们分隔开。MANIFEST由于子 Manifest 可以使用任何文件名,因此 Manifest 查找算法不能通过存储所有
将 ChangeLogs 注入签出
文件来缩短过程。相反,它需要沿着顶级 Manifest 中的
条目重新跟踪相关的子 Manifest 文件。IGNORE新 Manifest 格式考虑的问题之一是将历史和自动生成的 ChangeLog 注入存储库。我们通常不包含这些文件,以减小检出大小。但是,一些用户对此表现出兴趣,Infra 正在努力通过额外的 rsync 模块提供它们。
将 distfile 校验和与文件校验和分离
如果这些文件被注入存储库,它们将导致 Manifest 的验证失败。为了解决这个问题,Infra 可以提供
- 条目以允许它们存在。
- 当前 Manifest 格式的另一个问题是,获取文件的校验和与本地文件的校验和合并到包目录内的单个文件中。具体指出,
由于 distfile 有时在不同的包之间重复使用,因此重复的校验和是多余的 [24]。DIST镜像管理员对使用单个工具验证所有 distfile 的可能性感兴趣。
此规范没有为这个问题提供一个干净的解决方案。它在技术上允许将
散列算法
条目移动到更高级别的 Manifest,但这种解决方案的用处值得怀疑。
但是,对于第二个问题,我们可能会交付一个专门的工具来使用此 Manifest 格式。
最初,此 GLEP 没有正式指定完整的哈希算法集。相反,它只列出了(信息性地)在编写时已使用的名称。由于强制一致使用算法名称对于互操作性很重要,因此在 1.3 版中进行了更改。hashlib由于更新 GLEP 所需的努力与新哈希算法被广泛部署所需的时间相比很小,因此在添加新的哈希方法之前,需要更新 GLEP。
建议的命名基于 Python
清单压缩
模块,因为大多数 Gentoo 工具都是用 Python 编写的。这些名称被转换为与 Manifest 中哈希函数的历史命名匹配。MANIFEST实现允许支持和使用 Manifest 文件中列出的哈希子集。这既可以用于避免在非高性能系统上计算多个哈希的开销,也可以用于优雅地处理向新哈希的过渡。
对 Manifest 压缩的支持是在对文件格式进行最少更改的情况下引入的。该
条目需要提供实际的(压缩)文件路径以与其他文件条目兼容,并避免混淆。
顶级 Manifest 文件的压缩已被禁止,因为该规范目前没有提供任何方法来验证文件,然后再解压缩。如果顶级 Manifest 被压缩,工具将不得不解压缩文件,然后才能验证其内容。这使得恶意第三方可以通过提供暴露解压缩程序漏洞或压缩炸弹的压缩 Manifest 来攻击系统。OpenPGP 明文签名涵盖 Manifest 的内容,因此与它们一起压缩。使用独立签名的可能性已被考虑,但由于轻微的收益而被拒绝,因为它不必要的复杂。从技术上讲,可以通过将所有数据移动到顶级目录中的压缩子 Manifest(例如MANIFESTManifest.sub.gz
)中来实现类似的结果,并在已签名的未压缩顶级 Manifest 中包含此文件的DIST条目。
关于在解压缩后为 Manifest 内容的校验和添加附加条目的存在进行了辩论。但是,如果只有压缩文件存在,则对未压缩文件的普通条目将令人困惑。此外,已经指出,
性能注意事项
条目也没有未压缩变体。
该规范允许使用不同的压缩方式共存同一 Manifest 文件的多个变体,以实现历史兼容性。但是,如果仍然需要存在未压缩变体,则包含压缩 Manifest 文件似乎没有任何实际的好处。提供不同的压缩变体在技术上可以提高互操作性,尽管可以通过使用更常见的支持格式(例如 gzip)来实现相同的结果。
在每次同步时执行完整树验证会对最终用户系统带来一些性能问题。初始测试表明,在 btrfs 文件系统上进行冷缓存验证可能需要大约 4 分钟,而该过程主要受 I/O 限制。另一方面,可以预期验证将在同步后立即执行,利用暖文件系统缓存。
向后兼容性
本 GLEP 提供了保留向后兼容性的可选方法。为了保留向后兼容性,以下内容对于清单每个包目录中的文件
- 所有文件都必须由单个文件覆盖清单文件,
- 包使用的所有 distfiles 必须包含在内,
- 所有在files/子目录内的文件需要使用AUX标签(而不是DATA),
- 所有.ebuild文件需要使用EBUILD标签,
- 该标签,用于和metadata.xml文件需要使用MISC标签,
- Manifest 可以被签名以提供真实性验证,
- 必须始终存在未压缩的 Manifest,并且可以存在具有相同内容的压缩 Manifest。
一旦向后兼容性不再是问题,上述内容不再需要保持,并且可以删除已弃用的标签。
致谢
感谢所有对本 GLEP 的创建做出宝贵贡献的人。这包括但不限于
- Robin Hugh Johnson,
- Ulrich Müller。
此外,感谢 Robin Hugh Johnson 提供了最初的 MetaManifest GLEP 系列,该系列既是灵感来源,也是本 GLEP 中使用的大量概念的来源。递归地,也感谢所有对原始 GLEP 做出贡献的人。
参考文献
[1] | (1, 2) GLEP 44: Manifest2 格式 (https://gentoolinux.cn/glep/glep-0044.html) |
[2] | GLEP 57: Gentoo 软件分发安全 - 概述 (https://gentoolinux.cn/glep/glep-0057.html) |
[3] | (1, 2) GLEP 58: Gentoo 软件分发安全 - 基础设施到用户分发 - MetaManifest (https://gentoolinux.cn/glep/glep-0058.html) |
[4] | GLEP 59: Manifest2 哈希策略和安全影响 (https://gentoolinux.cn/glep/glep-0059.html) |
[5] | GLEP 60: Manifest2 文件类型 (https://gentoolinux.cn/glep/glep-0060.html) |
[6] | GLEP 61: Manifest2 压缩 (https://gentoolinux.cn/glep/glep-0061.html) |
[7] | RFC 4880: OpenPGP 消息格式 (https://www.rfc-editor.org/rfc/rfc4880) |
[8] | Unicode 标准 (https://unicode.org/versions/latest/) |
[9] | RFC 3339: Internet 上的日期和时间:时间戳 (https://www.rfc-editor.org/rfc/rfc3339) |
[10] | (1, 2) 包管理器规范:依赖关系规范格式 - SRC_URI (https://projects.gentoo.org/pms/6/pms.html#x1-940008.2.10) |
[11] | Ebuild 文件格式 - Gentoo 开发指南 (https://devmanual.gentoolinux.cn/ebuild-writing/file-format/#file-naming-rules) |
[12] | RFC 7693: BLAKE2 密码哈希和消息认证码 (MAC) (https://www.rfc-editor.org/rfc/rfc7693) |
[13] | RFC 1321: MD5 消息摘要算法 (https://www.rfc-editor.org/rfc/rfc1321) |
[14] | 哈希函数 RIPEMD-160 (https://homes.esat.kuleuven.be/~bosselae/ripemd160.html) |
[15] | FIPS PUB 180-4: 安全哈希标准 (SHS) (https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) |
[16] | FIPS PUB 202: SHA-3 标准:基于置换的哈希和可扩展输出函数 (https:://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) |
[17] | RFC 6986: GOST R 34.11-2012:哈希函数 (https://www.rfc-editor.org/rfc/rfc6986) |
[18] | Paulo S. L. M. Barreto,WHIRLPOOL 哈希函数(已存档于 2017-11-29)(https://web.archive.org/web/20171129084214/http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html) |
[19] | RFC 1952: GZIP 文件格式规范版本 4.3 (https://www.rfc-editor.org/rfc/rfc1952) |
[20] | RFC 草案:Lzip 压缩格式和 'application/lzip' 媒体类型 (https://datatracker.ietf.org/doc/html/draft-diaz-lzip) |
[21] | .xz 文件格式 (https://tukaani.org/xz/xz-file-format.txt) |
[22] | RFC 8878: Zstandard 压缩和 'application/zstd' 媒体类型 (https://www.rfc-editor.org/rfc/rfc8878) |
[23] | Cappos,J 等人。(2008)。“对包管理器的攻击”(https://www2.cs.arizona.edu/stork/packagemanagersecurity/attacks-on-package-managers.html) |
[24] | 根据 Robin H. Johnson 的说法,在撰写本文时,所有 DIST 条目的 8.4% 是重复的,占 DIST 条目总数 25 MiB 中的 2 MiB。 |
[25] | gemato:Gentoo Manifest 工具 (https://github.com/mgorny/gemato/) |
版权
本作品根据知识共享署名-相同方式共享 4.0 国际许可协议授权。要查看此许可协议的副本,请访问 https://creativecommons.org/licenses/by-sa/4.0/.