Linux v5.8 安全特性深度解析:从 BTI 到 CAP_BPF 的内核加固

本文详细介绍了Linux v5.8版本中的多项安全增强特性,包括ARM64的BTI和SCS支持、新的内核能力分离(CAP_PERFMON与CAP_BPF)、网络RNG改进及内核地址泄露修复等,全面提升系统安全性。

Linux v5.8 安全特性详解

Linux v5.8 于 2020 年 8 月发布。以下是我关注到的各项安全特性的总结:

arm64 分支目标识别(BTI)

Dave Martin 添加了对 ARMv8.5 分支目标指令(BTI)的支持,该功能在用户空间通过 execve() 启用,并在内核中全程启用(这需要手动标记大量非 C 代码,如汇编和 JIT 代码)。

有了 BTI,攻击者无法再使用跳转导向编程(JOP,即通过跳转和调用链接代码片段)。攻击者的代码必须进行直接函数调用。这基本上将攻击者可用的“可用”代码从内核文本中的每个字减少到仅函数入口(或跳转目标)。这是一种“低粒度”的前向边缘控制流完整性(CFI)功能,既重要(因为它大大减少了攻击中可用的潜在目标)又廉价(硬件实现)。这是实现强 CFI 的良好第一步,但(如我们在 CFG 等功能中看到的)通常不足以阻止有动机的攻击者。“高粒度” CFI(使用更具体的分支目标特征,如函数原型,来跟踪预期的调用站点)尚未得到硬件支持,但软件版本将通过 Clang 的 CFI 实现在未来推出。

arm64 影子调用栈(SCS)

Sami Tolvanen 实现了 Clang 影子调用栈(SCS)的内核版本,保护内核免受返回导向编程(ROP)攻击(即通过返回链接代码片段)。这种后向边缘 CFI 保护通过维护第二个专用栈指针寄存器(x18)并将返回地址副本存储在单独的“影子栈”中来实现。这样,操纵常规栈的返回地址将无效。(由于返回地址的副本继续存在于常规栈中,因此无需对回溯转储等进行更改。)

值得注意的是,与基于硬件的 BTI 不同,这是一种软件防御,依赖于影子栈的位置(即 x18 的值)保持秘密,因为内存可以直接写入。Intel 的硬件 ROP 防御(CET)使用不可直接写入的硬件影子栈。ARM 针对 ROP 的硬件防御是 PAC(实际上设计为任意 CFI 防御——也可用于前向边缘),但这取决于拥有 ARMv8.3 硬件。预计在 PAC 可用之前将使用 SCS。

内核并发清理器(KCSAN)基础设施添加

Marco Elver 添加了对内核并发清理器(KCSAN)的支持,这是一种通过 CONFIG_KCSAN 在内核中查找数据竞争的新调试基础设施。这立即发现了真实错误,其中一些修复也已落地。更多详情请参阅 KCSAN 文档。

新能力

Alexey Budankov 添加了 CAP_PERFMON,旨在允许访问 perf()。其理念是,该能力仅允许进程读取运行中的内核和系统的方面。不再需要通过更强大的 CAP_SYS_ADMIN 能力进行访问,后者有多种方式可以更改内核内部。这允许在控制内核机密性(通过 CAP_PERFMON 进行读取访问)与控制完整性(通过 CAP_SYS_ADMIN 进行写入访问)之间进行分离。

Alexei Starovoitov 添加了 CAP_BPF,旨在将 BPF 访问与全能的 CAP_SYS_ADMIN 分离。它设计用于与 CAP_PERFMON(用于类似跟踪的活动)和 CAP_NET_ADMIN(用于网络相关活动)结合使用。对于可能更改内核完整性的操作(即写入访问),仍需要 CAP_SYS_ADMIN

网络随机数生成器改进

Willy Tarreau 使网络代码的随机数生成器更难以预测。这将进一步挫败攻击者从外部恢复 RNG 状态的尝试,从而可能避免网络会话被劫持(通过正确猜测数据包状态)。

修复各种内核地址泄露给非 CAP_SYSLOG 用户

我修复了几种内核地址仍被泄露给非特权(即非 CAP_SYSLOG)用户的情况,尽管通常仅通过奇怪的边缘案例。在重构了 /sys/proc 中文件的能力检查方式后,内核模块部分、kprobes 和 BPF 泄露得到了修复。(尽管在这样做时,我在正确修复之前短暂使情况变得更糟。哎呀!)

RISCV W^X 检测

继最近在 RISCV 上启用严格内核内存保护的工作之后,Zong Li 现在添加了对 CONFIG_DEBUG_WX 的支持,与其他架构类似。内核中的任何可写和可执行内存区域(攻击者的可爱目标)将在启动时大声通知,以便进行纠正。

execve() 重构继续

Eric W. Biederman 继续致力于 execve() 重构,包括消除用于定位二进制处理程序的频繁有问题的递归。我利用这个机会修订了一些旧的 binfmt_script 回归测试,并将它们纳入内核自测中。

多个 /proc 实例

Alexey Gladkov 现代化了 /proc 内部结构,并提供了一种在同一 PID 命名空间中挂载多个 /proc 实例的方法。这允许拥有多个 /proc 视图,并启用不同功能。(包括新添加的 hidepid=4subset=pid 挂载选项。)

set_fs() 移除继续

Christoph Hellwig 与 Eric W. Biederman、Arnd Bergmann 等人一直在努力完全移除内核的 set_fs() 接口,该接口由于内核认为应访问哪个地址空间的奇怪混淆而长期成为安全漏洞的来源。除了较低级别的每架构信号处理代码之外,这还需要触及 ELF 加载器和网络代码的各种部分。

本地 64 位不再使用 READ_IMPLIES_EXEC

READ_IMPLIES_EXEC 标志是在引入 x86_64 时处理不可执行(NX)内存的变通方法。它设计为一种标记内存区域的方式:“既然我们不知道这个内存区域是否预期可执行,我们必须假设如果我们需要读取它,我们也需要被允许执行它”。它主要设计用于栈内存(可能存放蹦床代码),但会延续到所有 mmap() 分配中,这意味着有时会向寻找可执行内存的攻击者暴露较大的攻击面。虽然通常这在正确标记其 ELF 部分为 NX 的现代系统上不会引起问题,但仍有一些尴尬的边缘情况。我通过将 READ_IMPLIES_EXEC 与 x86 和 arm/arm64 上的 ELF PT_GNU_STACK 标记分离,并声明本地 64 位进程在 x86_64 和 arm64 上永远不会获得 READ_IMPLIES_EXEC 来修复此问题,这与其他本地 64 位架构的行为一致,这些架构最初就没有实现 READ_IMPLIES_EXEC

数组索引边界检查继续

作为在内核中使用现代灵活数组的持续工作的一部分,Gustavo A. R. Silva 添加了 flex_array_size() 辅助函数(作为 struct_size() 的姊妹函数)。零/一成员到灵活数组的转换继续进行了超过一百次提交,因为我们逐渐接近能够使用 -Warray-bounds 进行构建。

scnprintf() 替换继续

Chen Zhou 加入 Takashi Iwai,继续用 scnprintf() 替换可能不安全的 sprintf() 使用。修复所有这些将确保内核避免令人讨厌的缓冲区连接意外。

以上就是全部内容!如果您认为还有其他内容应在此提及,请告诉我。接下来:Linux v5.9。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计