RFC-0159:只执行内存

RFC-0159:仅执行内存
状态已接受
领域
  • 内核
  • 工具链
说明

支持映射只执行内存。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-03-29
审核日期(年-月-日)2022-05-10

摘要

本文档提议对内核 API 进行更改,以支持 即只执行细分,方法是添加新的特征签入 zx_system_get_features 并更改 launchpadprocess_builder 加载器以及 Fuchsia 的树内 libc 中的动态链接器,以支持“--x” 细分。它提出了最终内核支持映射的计划。 只执行页面。

我们通常不需要在可执行内存加载后对其进行读取。 默认启用只执行代码可提高 Fuchsia 用户空间的安全性 并进一步推动了最小权限工程最佳实践。

设计初衷

ARMv7m 中的 ARM MMU 中添加了对只执行页面的支持,并允许页面 要映射的内存区域仅可执行, 可写入。尽管可写代码页一直被认为是威胁 研究表明,让代码保持可读状态会导致 带来不必要的风险。具体而言,阅读代码页面通常是 攻击链中的一步,并防止代码被读取阻碍 对手。请参阅可读代码安全性。此外, 支持只执行页面非常适合 Fuchsia 的权限模型, 与最小权限原则更加契合:代码通常 需要读取,但只需执行。

利益相关方

教员

  • cpu@google.com

审核者

  • phosek@google.com
  • mvanotti@google.com
  • maniscalco@google.com
  • travisg@google.com

背景

只执行内存

只执行内存 (XOM) 用于描述既未读取也未读取的内存页面 并且只能执行。ARMv7m 及更高版本具有原生支持 但在旧版 ISA 上有一些注意事项。进一步讨论 (采用 XOM 和 PAN)。

本文档几乎只关注 AArch64,但其实现 与架构无关当其他组织对硬件和工具链支持成熟时 都能够轻松利用“只执行”模式 支持 Fuchsia。

代码页面的权限

最初,计算机支持对物理内存的直接内存访问, 任何检查或保护措施。MMU 的引入提供了一个关键的抽象概念, 以虚拟内存的形式实现,具体方法是将程序的内存视图与 底层物理资源这有助于更灵活、更安全 通过让操作系统实现者提供强有力的隔离机制来 通过进程抽象层在程序之间传递如今的 MMU 提供 一些关键设施,如分页内存、快速地址转换、 以及权限检查它们还可让用户有效控制 内存区域可通过通常的 控制是否可以读取、写入或执行内存页面。这是一个密钥 属性,因为它限制了 程序通过硬件强制执行的方式滥用系统资源 权限检查。

可写且可执行的内存尤为危险,因为它 为攻击者提供了一种简单的方式来执行任意代码 修复常见漏洞,例如缓冲区溢出。因此,许多操作系统 配置明确禁止页面既可写入又可执行 (W^X)。十多年来,这一直是标准,OpenBSD 增加了对 W^X,在 2003 年采用 OpenBSD 3.3 openbsd-wxorx。另请参阅 SELinux W^X 政策 selinux-wxorx。可写代码对于即时 (JIT) 等用途非常有用 编译,即在运行时将可执行指令写入内存。拥有 可以禁止 W|X 网页,此时需要使用 JIT 来解决这个问题。一个简单的方法是 将代码写入不可执行页面,然后再更改页面保护 即,通过 mprotectzx_vmar_protect,可执行但不能写入 example-fuchsia-test.几乎在所有情况下,W|X 的网页 宽容模式。同样,可执行页面也很少需要被读取。请参阅 异常。“允许对可执行页面执行读取操作”现为 通常没必要使用,也不应该是默认设置。

可读代码

由于 ARM 的指令宽度固定,因此直接值的大小 限制条件。因此,加载是使用 PC 相对地址处理完成的。要获得 伪指令 ldr Rd, =imm 会以字面量形式发出 imm 池附近。这与 XOM 不兼容,因为它 将数据放入文本部分,该部分必须可读。当搜索用于 字面量池,以确保不会读取可执行片段, 在 Zircon 中发现 ldr Rd, =imm 的一些用法, 已移除。Clang 不会针对 aarch64 使用字面量池,而是会发出 多个指令来创建大型即时消息。Clang 具有 -mexecute-only 标志和别名 -mpure-code,但它们仅在 arm32 上才有意义,因为 以 aarch64 为目标平台时,这些标记是固有的。

示例:大型中级课程

此示例展示了 Clang 如何编译此 C 代码,以便在给定不同的情况下 目标为 clang-example。第一行显示 aarch64,底部显示 arm32:

uint32_t a() {
    return 0x12345678u;
}
# -target aarch64
a:
    mov w0, #22136
    movk w0, #4660, lsl #16
    ret
# -target arm
a:
    ldr r0, .LCPI0_0
    bx lr
.LCPI0_0:
    .long 305419896

XOM 和 PAN

特权访问 (PAN) 是 ARM 芯片上的一项安全功能,可以防止 从内核模式对用户页面进行正常内存访问。它有助于防止 潜在的内核漏洞,因为内核无法接触用户内存 加载或存储指令。操作系统需要改为 PAN 或者按照 ldtrsttr 说明访问这些网页。PAN 为 目前尚未对 Fuchsia 启用,但已计划在 zircon pan-fxb

Aarch64 页面表条目包含 4 个用于控制页面权限的相关位。2 次 位用于用户和特权执行永不执行。其余两个用于 来说明两种访问权限级别的读取和写入页面权限。一个 只执行映射移除了读写权限,但允许用户 执行。

ARMv8 参考手册中的下表显示了可能的内存保护 仅使用 4 个可用位EL0 是用户空间的异常级别。行 0 和 2 展示了如何创建用户空间只执行页面。请参阅表 D5-34 的第 1 阶段 ARMv8 参考手册。

UXN PXN AP[2:1] 从更高的异常级别进行访问 通过 EL0 访问
0 1 00 R、W X
0 1 01 R、W R、W、X
0 1 10 R X
0 1 11 R R、X

遗憾的是,PAN 用于决定某个网页是否应获得特权的算法 无障碍功能检查网页是否易于用户理解。从 PAN 的角度来看, 用户只执行的页面看起来像是特权映射。这样,内核 在不应该访问用户内存的位置,从而绕过 PAN 的 以及使 PAN 和 XOM 不兼容 pan-issue。这会 因此,在将来使用 PAN 对试图利用 但它仍然有助于检测 内核 bug。

此问题导致 Linux 和 Android 不再支持 XOM。这是 对于在 Android 中无限期停止支持的 Android 用户来说尤其明显 Android 10 中引入的 11 版本,并设为所有 aarch64 二进制文件的默认版本 linux-revert。他们打算重新启用此功能 这种方法已经非常普遍地解决了这个问题 帧。

此后,ARM 提议了一种具有“增强型”PAN 或 ePAN 的解决方案,这会更改 PAN 以检查页面是否可由用户读取,以及是否不可执行。 遗憾的是,具有此功能的硬件可能未在任何针对 设备。自此之后,Linux 重新添加了 ePAN 已改为 linux-re-land。我们无法控制是否在设备上支持 ePAN 并且与 PAN 和 XOM 的不兼容不应阻止内核的 PAN 的实现 了解详情

从图 2 中,没有可以读取权限的可能配置 从内核剥离出来。唯一的例外是 PAN,它会导致 在内核尝试处理用户可读页面时出现异常。因此 无法为内核创建只执行映射,因为 内核无法将页面标记为可执行但不可读取。因此,只有 可用于为用户空间进程创建只执行映射。

以 XOM 硬件为目标

ELF 中的分段权限指示运行代码所需的权限 正确。换言之,软件不需要在构建时知道 运行该程序的硬件是否支持 XOM。相反,它应该 如果 XOM 不需要读取代码页面,则可以无条件使用 XOM。这取决于 操作系统和加载器在最大程度上强制执行这些权限 允许 elf-segment-perm

虚拟内存权限

POSIX 指定 mmap 可以允许对以下页面的读取访问权限:PROT_READ 尚未明确设置 posix-mmap。x86 和 macOS 上的 Linux 和 macOS 在 M1 芯片上,仅使用 PROT_EXEC 从 mmap 请求页面时不会失败 并改为将页面设为 PROT_READ | PROT_EXEC。这些实现具有 在尊重用户请求方面能够“尽力而为”的系统调用。 另一方面,Fuchsia 系统调用始终会明确其可以实现的 无法兑现。zx_vmar_* 系统调用不会静默地提升 这类网页(如 POSIX 同类网页)是该标准允许的。正在请求 不带 ZX_VM_PERM_READ 的网页当前始终会因为硬件和 操作系统不支持在没有读取权限的情况下映射页面。优雅 过渡到支持包含只执行片段和用户空间的二进制文件 分配只执行内存的程序需要一种方法来检查 操作系统可以在请求只执行页面之前映射它们。

可读代码安全性

许多攻击依赖于通过阅读 代码页面来查找“小工具”或感兴趣的可执行代码。地址空间 布局随机化 (ASLR) 是操作系统在加载网页时 位于进程地址空间中半随机位置的二进制段。它的用途是 来阻止依赖于代码位置、 或其他数据保存在内存中使代码不可读可进一步减少攻击 。

利用代码重用攻击(例如“return-to-libc”rtl-attack) 已知地址。要返回或指定 libc 因为其中包含对攻击者有用的丰富功能, 因为该进程很可能会链接到 libc。过去 证明典型程序中的可用小工具 图灵完备,让攻击者能够执行任意代码。

在许多情况下,对手的目标是获得一个 shell。ASLR 使这些标签 因为函数的地址不同,所以攻击的难度会增大 程序调用不过,ASLR 并不是全面的缓解措施, 因为攻击者可以通过读取代码页面来查找 否则无法通过在二进制文件中查看其地址来获知。XOM 让 ASLR 不可能以这种方式被破坏,并且攻击者需要使用 另一种查找特定代码页位置信息的方法。

通用表示法

‘rwx/r-x/–x’

这些权限表示 ELF 细分的权限,它们会映射到进程 具有相应权限的地址空间。这种表示法通常用于 以及按工具细分的 ELF 细分, readelf。r、w 和 x 分别表示读取、写入和执行,“-”表示 未授予权限。只执行细分将显示“--x” 权限。

R^X、W|X 等...

如上所述,R、W 和 X 分别指读取、写入和执行。“^”和“|”类似于 C xor 和 or 运算符。R^X 被读取为“读取 xor 执行”。

“ax”

这是编译器语法,可将某个部分标记为“已分配”且可执行。 目前,链接器会将“ax”部分放入“r-x”段中。通过 lld 中的 --execute-only 标志会改为将这些片段标记为“--x”。

设计

为了通过支持 XOM 提高用户空间程序的安全性,我们 工具链和加载器需要更新。Clang 驱动程序需要 请将“--execute-only”标记传递给链接器,以确保“ax”部分 否则映射到“r-x”细分,将映射到“--x”细分。通过 加载器还需要更改所有请求的 权限至少包含 read,因为这将不再是 true。

由于只能在支持 ePAN 的硬件上使用 XOM,因此我们需要 以便顺利地支持过渡我们有两种选择:

  1. 尽可能像许多 mmap 实现一样更改 vmar_* 函数
  2. 创建一种方法来查询内核,前提是它支持只执行映射,并且 如果 XOM 并非 可用。
  3. 添加了一个新的 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 标志,以供加载器与 “--x”细分

在任何情况下,系统可能都会静默提升权限。通过 第一种方式是最简单的,加载器只需 来移除其健全性检查第二个选项 我们只需要在加载器中添加简单的检查,然后再决定 向操作系统请求的内存权限第三个选项非常有用 不容易在用户代码中出错。

第一个选项最终会破坏 Fuchsia 目前与 始终明确说明系统调用可以遵循和不能遵循的内容。 第 2 个和第 3 个选项最终也会导致对内存权限的处理不明确 。不过,这符合 ELF 规范。细分 权限没有以 1:1 的方式指定分配给 而是内存必须至少拥有哪些权限 确保程序正常运行ELF 加载器在有权映射范围内 将“--x”段转换为“r-x”内存 elf-segment-perm

打破 Fuchsia 当前显式系统调用合同的第一个选项 处理方式并不理想。选项 2 和选项 3 都有价值,且 此 RFC 中建议的将基于这两个选项。

实现

添加系统调用

系统将添加一个新标志 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED, 各种 zx_vmar_* 系统调用,这些调用在 options 中使用权限标志 如果 XOM 不受支持,则会隐式添加读取权限。 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 逻辑上仅适用于 ZX_VM_PERM_EXEC 而不是 ZX_VM_PERM_READ 不同的系统调用, 不会将其视为不变的。您可以放心 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 与标志的任何其他组合, 在上下文中,系统仅会将ZX_VM_PERM_READ 无法映射只执行页面。

系统将为以下账号添加新的kind值:ZX_FEATURE_KIND_VM zx_system_get_features,这将产生一个类似于 ZX_FEATURE_KIND_CPU。我们还将推出一项新功能 ZX_VM_FEATURE_CAN_MAP_XOM。当前实现将始终保持 位为 false,因为 XOM 直到稍后才会启用。该域名不会 因为“r-x”内存权限对“--x”区段有效, 仍然便于用户空间查询这项功能

系统加载器 ABI 变更

当前和未来的加载器将确保“--x”可以加载到内存中 即使目标不支持 XOM 也是如此。加载器会将 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED(在映射只执行细分时)。

已交付动态链接器 ABI 更改

同样,SDK 附带的 Fuchsia libc 中的动态链接器也 为“--x”片段分配内存时,必要时提升权限 尽在 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED

编译器工具链变更

Clang 驱动程序也将更改为始终将 --execute-only 传递给 链接器在定位 aarch64-*-fuchsia 时使用。我们还需要一种 则很可能通过向代码中添加一个新的“--no-execute-only” 以便程序可以轻松停用新的默认行为。

内核 XOM 实现

当支持 ePAN 的硬件到达后,内核便可处理对 内存页面的大小,使其仅包含 ZX_VM_PERM_EXECUTE。arm64 user-copy 实现可能需要更新,以确保与用户内存 访问权限受到限制。应更新 user_copy,以使用 ldtr,并且 sttr 指令。这样可以确保用户无法诱骗内核读取 显示不可读的页面。此外,内核还会对映射做出假设 但这需要更改 适当的选择。这项工作将在稍后完成。

不必要的更改

zx_process_read_memory 无需更改,调试程序应该可以正常使用 通常用于调试只执行二进制文件。zx_process_read_memory忽略 读取页面的权限,并且只检查 进程句柄具有 ZX_RIGHT_READZX_RIGHT_WRITE

zx_vmar_protect将继续照常运行。最值得注意的是 意味着进程可以在以下情况下通过读取权限保护其代码页面: 必要时。

性能

效果预计不会受到任何影响。

安全

在内核中实现 XOM 之前,带有“--x”段的二进制文件只会 与使用“r-x”段的等效二进制文件一样安全。支持 XOM 后 同时由硬件和操作系统(选择使用只执行内存的程序) 将变得更加安全请参阅代码权限部分 页面XOM 和 PAN 以及可读性 代码安全性

隐私权

除了安全性中列出的注意事项之外,无需考虑任何其他因素。

测试

当我们强制 XOM 时,zx_system_get_features 会进行小规模测试 支持在内核中实现,我们可以在构建时知道对 要返回的系统调用。

将测试 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 能否生成网页 当 zx_system_get_features 报告操作系统无法读取该内存值时, 创建只执行页面。

同样,除模糊测试外,elfload 库没有任何实际测试 而不会测试预期功能相反,其功能 并由依赖于它的其他组件进行测试。应在此处添加测试 确保“--x”正确映射了细分。process_builder 库的作用 具有测试,这可确保它能正确请求可读取且可执行的 内存不足。

系统不会直接测试对当前动态链接器的更改。一种新的 我们将对动态链接器进行大量测试,包括 多个“--x”细分

对 Clang 驱动程序的更改将在上游 LLVM 中进行测试。

我们还会设置测试配置,以便在测试机器人上启用 XOM,即使 硬件没有 ePAN,否则我们将不会启用 XOM。这个 可以帮助我们捕捉到读取其代码页并需要选择 而不再是只执行模式

文档

系统将记录对 zx_system_get_features 的更改以及 用户空间想要使用 ZX_VM_FEATURE_CAN_MAP_XOM。同样,新的 还将记录 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 标志。更改为 各种加载器和 Clang 驱动程序默认值不会在外部进行记录 。

缺点、替代方案、未知

目前未知的树代码当前和未来在多大程度上依赖于可执行文件 代码可读性这可以通过在文本中使用 手写汇编代码、从其他工具链或程序编译的代码 内省。无论如何,需要有可读代码页面的程序 仍会受益,因为其共享库依赖项(包括 libc) 只执行标记为“只执行”将 Clang 工具链更改为默认只执行 片段会破坏依赖于可读代码的程序。没有简单的方法 在构建时检查程序是否依赖于此行为。不过,一旦 发现计划需要“r-x”细分,选择停用默认的“--x” 也很简单

对于需要读取部分(而非全部)代码的程序, 现有的工具无法轻松支持这一点。--execute-only linker 标志 会剥离任何可执行段的读取权限, 将某一部分标记为需要阅读。希望出现这种行为的程序 需要彻底停用只执行

风险

Clang 驱动程序可能会默认使用 --execute-only 和代码 从“--x”段读取的数据不会被损坏,除非硬件和内核 对 XOM 的支持这会给应用带来潜在的向前兼容性问题, 没有更改过的软件测试将在树型软件中进行,但大多数 则不太可能发生在树代码之外。

早期艺术作品和参考资料

由于在许多 POSIX 中对 mmap 权限标志的处理不明确 无需进行模拟 zx_system_get_features(ZX_FEATURE_KIND_CAN_MAP_XOM, &feature)

Darwin 在较新的 Apple 芯片上支持 XOM,但其实现方式更多, 使用专有硬件功能实现稳健。他们的芯片有硬件支持 从内核和用户内存中剥离各个权限位。时间是 在 macOS 中未启用用户空间。apple-xom