完整性策略实施 (IPE) - 内核文档

注意

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

历史动机

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

  1. IMA + EVM 签名

  2. DM-Verity

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

  1. 保护额外的攻击媒介

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

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

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

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

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

  1. 性能

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

  1. 签名的简易性

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

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

  • 签名支持基于 x.509 的签名基础设施。

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

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

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

  3. 策略实施必须具有类似 permissive 的模式。

  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 内部,当安装真正的 rootfs 但尚未传输到该 rootfs 时,它会部署并激活一个信任新 root 文件系统的策略。这可以防止在任何步骤中过度授权,并将内核策略保持在最小的大小。

启动

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

可更新的、无需重启的策略

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

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

为了允许从外部源进行更新,它可能是恶意的,因此此策略需要有一种方法来标识为受信任的。这是通过链接到内核中信任源的签名来完成的。任意地,这是 SYSTEM_TRUSTED_KEYRING,它是在内核编译时最初填充的密钥环,因为这与编译到其中的策略的作者与可以部署策略更新的实体是相同的期望相符。

反回滚/反重放

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

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

最初,IPE 的策略可以具有 policy_version,该 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 的集成测试套件,可以测试用户界面和实施功能。