RFC-0159:仅执行内存 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 支持映射只执行内存。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2022-03-29 |
审核日期(年-月-日) | 2022-05-10 |
摘要
本文档提议对内核 API 进行更改,以支持
即只执行细分,方法是添加新的特征签入
zx_system_get_features
并更改 launchpad
和 process_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 来解决这个问题。一个简单的方法是
将代码写入不可执行页面,然后再更改页面保护
即,通过 mprotect
或 zx_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
或者按照 ldtr
和 sttr
说明访问这些网页。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,因此我们需要 以便顺利地支持过渡我们有两种选择:
- 尽可能像许多
mmap
实现一样更改vmar_*
函数 - 创建一种方法来查询内核,前提是它支持只执行映射,并且 如果 XOM 并非 可用。
- 添加了一个新的
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_READ
和 ZX_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