完整性策略执行 (IPE) - 内核文档

注意

这是针对开发人员而不是管理员的文档。如果您正在寻找有关 IPE 用法的文档,请参阅IPE 管理指南

历史动机

最初促使 IPE 实现的问题是创建锁定的系统。该系统将是天生安全的,并且对其可执行代码以及系统上对其功能至关重要的特定数据文件具有强大的完整性保证。这些特定的数据文件只有在通过完整性策略后才能读取。将存在强制访问控制系统,因此,必须保护 xattrs。这导致选择了什么来提供完整性声明。当时,考虑了两种主要机制,它们可以保证具有这些要求的系统的完整性

  1. IMA + EVM 签名

  2. DM-Verity

两种选项都经过仔细考虑,但是之所以选择使用 DM-Verity 而不是 IMA+EVM 作为 IPE 原始用例中的完整性机制,主要有三个原因

  1. 保护其他攻击媒介

  • 对于 IMA+EVM,如果没有加密解决方案,系统很容易受到针对上述特定数据文件的离线攻击。

    与可执行文件不同,读取操作(如对受保护数据文件的读取操作)不能强制进行全局完整性验证。这意味着必须存在某种形式的选择器来确定读取是否应强制执行完整性策略,或者是否不应强制执行。

    当时,这是使用强制访问控制标签完成的。IMA 策略将指示哪些标签需要完整性验证,这带来了一个问题:EVM 将保护标签,但是如果攻击者可以离线修改文件系统,则攻击者可以擦除所有 xattrs,包括用于确定文件是否应受完整性策略约束的 SELinux 标签。

    使用 DM-Verity,由于 xattrs 作为 Merkel 树的一部分保存,如果对受 dm-verity 保护的文件系统发生离线挂载,则校验和不再匹配,并且文件读取失败。

  • 由于用户空间二进制文件是在 Linux 中分页的,因此 dm-verity 还提供了针对恶意块设备的额外保护。在这种攻击中,块设备最初报告 IMA 哈希的适当内容,从而通过了所需的完整性检查。然后,在访问真实数据的页面错误上,将报告攻击者的有效负载。由于 dm-verity 将在页面错误发生时(以及磁盘访问时)检查数据,因此可以缓解此攻击。

  1. 性能

  • dm-verity 在读取块时按需提供完整性验证,而无需将整个文件读取到内存中进行验证。

  1. 签名的简便性

  • 无需两个签名(IMA,然后是 EVM):一个签名涵盖整个块设备。

  • 签名可以存储在文件系统元数据外部。

  • 签名支持基于 x.509 的签名基础架构。

下一步是选择一个策略来强制执行完整性机制。该策略的最低要求是

  1. 策略本身必须经过完整性验证(防止对其进行简单的攻击)。

  2. 策略本身必须能够抵抗回滚攻击。

  3. 策略执行必须具有类似允许的模式。

  4. 策略必须能够完全更新,而无需重新启动。

  5. 策略更新必须是原子的。

  6. 策略必须支持撤销以前编写的组件。

  7. 策略必须在任何时间点都可审计。

当时,作为唯一的完整性策略机制,IMA 根据这些要求进行了考虑,并且没有满足所有最低要求。考虑扩展 IMA 以涵盖这些要求,但最终出于两个原因被放弃

  1. 回归风险;许多这些更改将导致 IMA 的代码发生巨大变化,而 IMA 已经存在于内核中,因此可能会影响用户。

  2. IMA 在系统中用于测量和证明;将测量策略与本地完整性策略执行分离被认为是有利的。

由于这些原因,决定创建一个新的 LSM,其职责仅是本地完整性策略执行。

角色和范围

IPE,顾名思义,从根本上来说是一种完整性策略执行解决方案;IPE 不规定如何提供完整性,而是将该决定留给系统管理员,让他们通过他们选择的适合其各自需求的机制来设置安全标准。有几种不同的完整性解决方案可提供不同级别的安全保证;IPE 允许系统管理员在理论上为所有这些解决方案表达策略。

IPE 本身没有固有的机制来确保完整性。相反,可以使用更有效的层来构建可以保证完整性的系统。重要的是要注意,证明完整性的机制与强制执行该完整性声明的策略是独立的。

因此,IPE 的设计围绕

  1. 易于与完整性提供商集成。

  2. 平台管理员/系统管理员易于使用。

设计原理:

IPE 是在评估其他操作系统和环境中的现有完整性策略解决方案后设计的。在对其他实现的调查中,发现了一些缺陷

  1. 策略不是人类可读的,通常需要二进制中间格式。

  2. 隐式地将单个、不可自定义的操作作为默认值。

  3. 调试策略需要手动步骤来确定违反了哪个规则。

  4. 编写策略需要深入了解更大的系统或操作系统。

IPE 试图避免所有这些缺陷。

策略

纯文本

IPE 的策略是纯文本的。与其他 LSM 相比,这引入了稍大的策略文件,但解决了其他平台上某些完整性策略解决方案中出现的两个主要问题。

第一个问题是代码维护和重复问题。要编写策略,该策略必须采用某种形式的字符串表示形式(无论是结构化的、通过 XML、JSON、YAML 等等),以使策略作者能够理解正在编写的内容。在假设的二进制策略设计中,需要一个序列化器将策略从人类可读的形式写入二进制形式,并且需要一个反序列化器将二进制形式解释为内核中的数据结构。

最终,将需要另一个反序列化器将二进制形式转换回人类可读的形式,并保留尽可能多的信息。这是因为此访问控制系统的用户必须保留校验和和原始文件本身的查找表,以尝试了解在此系统上部署了哪些策略以及未部署哪些策略。对于单个用户,这可能还可以,因为旧策略几乎可以在更新生效后立即丢弃。对于管理成千上万甚至数十万台计算机集群的用户,它们具有多个不同的操作系统和多个不同的操作需求,这很快就成为一个问题,因为可能存在几年前的陈旧策略,从而很快导致需要恢复策略或资助庞大的基础设施来跟踪每个策略包含的内容。

现在有了三个单独的序列化器/反序列化器,维护成本变得很高。如果策略避免使用二进制格式,则只需要一个序列化器:从人类可读的形式到内核中的数据结构,从而节省了代码维护并保持了可操作性。

二进制格式的第二个问题是透明性问题。由于 IPE 根据系统资源的信任来控制访问,因此其策略也必须受到信任才能更改。这是通过签名完成的,从而需要将签名作为过程。签名作为一种过程,通常以高安全标准完成,因为任何签名的内容都可用于攻击完整性执行系统。同样重要的是,在签署某些内容时,签名者应了解他们正在签署的内容。二进制策略可能会使该事实变得模糊;签名者看到的是不透明的二进制 blob。另一方面,纯文本策略,签名者看到的是提交签名时的实际策略。

启动策略

如果配置得当,IPE 能够在内核启动且用户模式启动后立即强制执行策略。这意味着需要存储策略的某种级别才能在用户模式启动后立即应用。通常,该存储可以通过以下三种方式之一进行处理

  1. 策略文件位于磁盘上,内核会在导致执行决策的代码路径之前加载策略。

  2. 策略文件由引导加载程序传递给内核,内核解析该策略。

  3. 有一个编译到内核中的策略文件,该文件在初始化时进行解析和强制执行。

第一个选项存在问题:通常不鼓励内核读取用户空间的文件,并且在内核中非常罕见。

第二个选项也存在问题:Linux 在其整个生态系统中支持各种引导加载程序 - 每个引导加载程序都必须支持这种新方法,或者必须有一个独立的来源。它可能会导致对内核启动的更改比必要的更剧烈。

第三个选项是最好的,但重要的是要注意,该策略将占用其编译所在内核的磁盘空间。重要的是要使此策略足够通用,以便用户空间可以加载新的、更复杂的策略,但又要足够严格,以使其不会过度授权并导致安全问题。

initramfs 提供了一种建立此启动路径的方法。内核以最小策略启动,仅信任 initramfs。在 initramfs 内部,当真正的根文件系统被挂载但尚未转移到它时,它会部署并激活一个信任新根文件系统的策略。这可以防止在任何步骤中过度授权,并保持内核策略的最小大小。

启动

然而,并非每个系统都使用 initramfs 启动,因此编译到内核中的启动策略需要一些灵活性来表达如何为启动的下一阶段建立信任。为此,如果我们只是将编译的策略设为完整的 IPE 策略,它允许系统构建者适当地表达第一阶段的启动要求。

可更新、无重启策略

随着时间的推移,需求会发生变化(在以前受信任的应用程序中发现漏洞,密钥轮换等)。更新内核以满足这些安全目标并不总是合适的选择,因为更新并非总是无风险的,并且阻止安全更新会使系统容易受到攻击。这意味着 IPE 需要一个可以从内核外部的源完全更新的策略(允许撤销现有策略),(允许在不更新内核的情况下更新策略)。

此外,由于内核在调用之间是无状态的,并且从内核空间从磁盘读取策略文件是个坏主意(tm),因此必须在不重启的情况下进行策略更新。

为了允许从外部来源进行更新,它可能具有潜在的恶意性,因此此策略需要一种被识别为受信任的方法。这是通过一个链接到内核中信任源的签名来完成的。通常,这是 SYSTEM_TRUSTED_KEYRING,一个在内核编译时初始填充的密钥环,因为这符合上述编译策略的作者与可以部署策略更新的实体是同一实体的预期。

防回滚/防重放

随着时间的推移,会发现漏洞,并且受信任的资源可能不再受信任。IPE 的策略对此没有例外。可能会出现错误的策略作者在部署安全策略之前部署不安全策略的情况。

假设一旦不安全策略被签名,并且攻击者获取了不安全策略,IPE 需要一种方法来防止从安全策略更新回滚到不安全策略更新。

最初,IPE 的策略可以有一个 policy_version,它声明系统上所有可以激活的策略的最低所需版本。这将防止系统运行时回滚。

警告

但是,由于内核在启动之间是无状态的,因此此策略版本将在下次启动时重置为 0.0.0。系统构建者需要意识到这一点,并确保在启动后尽快部署新的安全策略,以确保攻击者部署不安全策略的机会窗口最小。

隐式操作:

只有当您考虑系统中多个操作之间存在混合安全级别时,隐式操作的问题才会显现出来。例如,考虑一个系统,该系统对系统上的可执行代码和特定的数据文件都具有很强的完整性保证,这些文件对其功能至关重要。在这个系统中,可能存在三种类型的策略

  1. 一种策略,其中策略中未能匹配任何规则会导致操作被拒绝。

  2. 一种策略,其中策略中未能匹配任何规则会导致操作被允许。

  3. 一种策略,其中当没有规则匹配时采取的操作由策略作者指定。

第一个选项可以制定如下策略

op=EXECUTE integrity_verified=YES action=ALLOW

在示例系统中,这对可执行文件很有效,因为所有可执行文件都应具有完整性保证,没有任何例外。问题出现在关于特定数据文件的第二个要求上。这将导致如下策略(假设每行按顺序评估)

op=EXECUTE integrity_verified=YES action=ALLOW

op=READ integrity_verified=NO label=critical_t action=DENY
op=READ action=ALLOW

如果您阅读文档,了解策略按顺序执行且默认值为拒绝,则这在某种程度上是明确的;但是,最后一行实际上将该默认值更改为 ALLOW。这是必需的,因为在实际系统中,存在一些未经验证的读取(想象一下附加到日志文件)。

第二个选项,匹配不到任何规则会导致允许,对于特定数据文件来说更清晰

op=READ integrity_verified=NO label=critical_t action=DENY

并且,与第一个选项一样,在执行场景中会不足,实际上需要覆盖默认值

op=EXECUTE integrity_verified=YES action=ALLOW
op=EXECUTE action=DENY

op=READ integrity_verified=NO label=critical_t action=DENY

这留下了第三个选项。与其让用户聪明地使用空规则覆盖默认值,不如迫使用户考虑他们的情况应采用的适当默认值并明确声明它

DEFAULT op=EXECUTE action=DENY
op=EXECUTE integrity_verified=YES action=ALLOW

DEFAULT op=READ action=ALLOW
op=READ integrity_verified=NO label=critical_t action=DENY

策略调试:

在开发策略时,了解违反策略的哪一行是有用的,以减少调试成本;将调查范围缩小到导致操作的准确行。一些完整性策略系统不提供此信息,而是提供评估中使用的信息。然后,这需要与策略进行关联以评估哪里出了问题。

相反,IPE 只会发出匹配的规则。这会将调查范围限制为精确的策略行(在特定规则的情况下),或部分(在 DEFAULT 的情况下)。这减少了在评估策略时观察到策略失败时的迭代和调查时间。

IPE 的策略引擎的设计方式也很容易让人们知道如何调查策略失败。每一行都按照编写的顺序进行评估,因此该算法对于人类来说很容易遵循,以重现步骤并可能导致失败。在其他调查过的系统中,加载策略时会进行优化(例如,排序规则)。在这些系统中,需要多个步骤才能进行调试,并且该算法对于最终用户来说可能并不总是清晰的,而无需先阅读代码。

简化的策略:

最后,IPE 的策略是为系统管理员而非内核开发人员设计的。IPE 不涵盖单独的 LSM 钩子(或系统调用),而是涵盖操作。这意味着系统管理员无需知道系统调用 mmapmprotectexecveuselib 必须具有保护它们的规则,他们必须简单地知道他们想要限制代码执行。这限制了由于缺乏对底层系统的了解而可能发生的绕过数量;而 IPE 的维护人员(作为内核开发人员)可以做出正确的选择,以确定某些内容是否映射到这些操作以及在什么条件下映射。

实施说明

匿名内存

在 IPE 中,匿名内存的处理方式与任何其他访问方式没有什么不同。当匿名内存映射为 +X 时,它仍然会进入 file_mmapfile_mprotect 钩子,但会有一个 NULL 文件对象。这像任何其他文件一样提交到评估。但是,所有当前的信任属性都将评估为 false,因为它们都是基于文件的,并且该操作与文件无关。

警告

当内核从用户空间缓冲区加载数据时,也会发生 kernel_load_data 钩子,该缓冲区不受文件的支持。在这种情况下,所有当前的信任属性也将评估为 false。

Securityfs 接口

每个策略的 securityfs 树有点独特。例如,对于标准的 securityfs 策略树

MyPolicy
  |- active
  |- delete
  |- name
  |- pkcs7
  |- policy
  |- update
  |- version

策略存储在 MyPolicy inode 的 ->i_private 数据中。

测试

IPE 具有用于策略解析器的 KUnit 测试。推荐的 kunitconfig

CONFIG_KUNIT=y
CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
CONFIG_PKCS7_MESSAGE_PARSER=y
CONFIG_SYSTEM_DATA_VERIFICATION=y
CONFIG_FS_VERITY=y
CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y
CONFIG_BLOCK=y
CONFIG_MD=y
CONFIG_BLK_DEV_DM=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_NET=y
CONFIG_AUDIT=y
CONFIG_AUDITSYSCALL=y
CONFIG_BLK_DEV_INITRD=y

CONFIG_SECURITY_IPE=y
CONFIG_IPE_PROP_DM_VERITY=y
CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
CONFIG_IPE_PROP_FS_VERITY=y
CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
CONFIG_SECURITY_IPE_KUNIT_TEST=y

此外,IPE 还有一个基于 Python 的集成测试套件,可以测试用户界面和强制执行功能。