Linux v5.8 安全特性深度解析
Linux v5.8 于2020年8月发布。以下是我关注到的各项安全特性的总结:
arm64 分支目标识别
Dave Martin 添加了对 ARMv8.5 分支目标指令(BTI)的支持,该功能在用户空间通过 execve()
启用,并在内核中全程启用(这需要手动标记大量非C代码,如汇编和JIT代码)。
有了这个功能,攻击者无法再使用面向跳转编程(JOP,即通过跳转和调用链式组合代码片段)。攻击者的代码必须进行直接函数调用。这基本上将攻击者可用的“可用”代码从内核文本中的每个字减少到仅函数入口(或跳转目标)。这是一个“低粒度”的前向边缘控制流完整性(CFI)特性,既重要(因为它大大减少了攻击中可用的潜在目标)又廉价(硬件实现)。这是实现强CFI的良好第一步,但(如我们在CFG等中看到的)通常不足以阻止有动机的攻击者。“高粒度”CFI(使用更具体的分支目标特征,如函数原型,来跟踪预期的调用站点)尚未得到硬件支持,但软件版本将通过Clang的CFI实现在未来到来。
arm64 影子调用栈
Sami Tolvanen 落地了 Clang 影子调用栈(SCS)的内核实现,该功能保护内核免受面向返回编程(ROP)攻击(即通过返回链式组合代码片段)。这种后向边缘CFI保护通过维护第二个专用栈指针寄存器(x18)并将返回地址的副本存储在单独的“影子栈”中来实现。这样,操纵常规栈的返回地址将无效。(并且由于返回地址的副本继续存在于常规栈中,无需对回溯转储等进行更改。)
值得注意的是,与BTI(基于硬件)不同,这是一种软件防御,依赖于影子栈的位置(即x18的值)保持秘密,因为内存可以直接写入。Intel的硬件ROP防御(CET)使用不可直接写入的硬件影子栈。ARM针对ROP的硬件防御是PAC(实际上设计为任意CFI防御——也可用于前向边缘),但这取决于拥有ARMv8.3硬件。预期是,在PAC可用之前将使用SCS。
内核并发清理器基础设施添加
Marco Elver 添加了对内核并发清理器(Kernel Concurrency Sanitizer)的支持,这是一个新的调试基础设施,通过 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=4
和 subset=pid
挂载选项。)
set_fs() 移除继续
Christoph Hellwig 与 Eric W. Biederman、Arnd Bergmann 等人一直在努力完全移除内核的 set_fs()
接口,该接口长期以来由于关于内核认为应该访问哪个地址空间的奇怪混淆而成为安全漏洞的来源。除了较低级别的每架构信号处理代码之类的事情之外,这还需要触及ELF加载器和网络代码的各种部分。
READ_IMPLIES_EXEC 不再用于原生64位
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。