Linux v5.7 安全特性
Linux v5.7 于 5 月底发布。以下是我关注到的各种安全相关内容的总结:
arm64 内核指针认证
虽然 ARMv8.3 CPU 的“指针认证”(PAC)功能已支持用户空间,但 Kristina Martsenko 现在在内核模式中实现了 PAC 支持。当前实现使用 PACIASP 保护保存的堆栈指针,类似于现有的 CONFIG_STACKPROTECTOR 功能,但速度更快。这也为签名和检查堆中存储的指针铺平了道路,以防御这些内存区域中的函数指针覆盖攻击。由于行为与传统堆栈保护器不同,Amit Daniel Kachhap 还为 PAC 添加了 LKDTM 测试。
BPF LSM
内核的 Linux 安全模块(LSM)API 提供了一种编写安全模块的方式,传统上用于实现各种强制访问控制(MAC)系统,如 SELinux、AppArmor 等。LSM 钩子数量众多,没有哪个 LSM 会使用所有钩子,因为某些钩子更为专业化(如 IMA、Yama、LoadPin 等使用的钩子)。然而,在此之前,无法从外部附加到这些钩子(甚至无法通过常规可加载内核模块实现),也无法构建完全动态的安全策略,直到 KP Singh 推出了使用 BPF 构建 LSM 策略的 API。通过 CONFIG_BPF_LSM=y,特权进程可以用 BPF 编写内核 LSM 钩子,实现完全自定义的安全策略(和报告)。
execve() 死锁重构
内核的进程启动代码中存在一些长期存在的竞争条件,可能导致 ptrace 死锁。过去多年中多次尝试修复这些问题,但 Eric W. Biederman 和 Ernd Edlinger 决定深入解决,并成功进行了一系列重构,拆分有问题的锁并重构其使用以消除死锁。借此机会,Eric 还将 exec_id 计数器扩展为 64 位,以避免计数器回绕,防止攻击者向通常无法访问的进程发送任意信号。
slub freelist 混淆改进
在 Silvio Cesare 观察到 CONFIG_SLAB_FREELIST_HARDENED 的 freelist 指针内容混淆实现存在一些弱点后,我改进了它们的位扩散,使得攻击需要显著更多的内存内容暴露才能破解混淆。在讨论中,Vitaly Nikolenko 指出 freelist 指针的位置也使其相对容易成为目标(用于泄露或覆盖),因此我将其从 slab 边缘移开,使其更难通过小尺寸溢出(通常针对 freelist 指针)达到。事实证明,内核中对 freelist 指针位置有一些假设,这些也需要清理。
RISCV 严格内核内存保护
Zong Li 为 RISCV 实现了 STRICT_KERNEL_RWX 支持。为了验证结果,继 v5.6 的通用页表转储工作之后,他推出了 RISCV 页转储代码。这意味着在运行调试内核(使用 PTDUMP_DEBUGFS 构建)时,检查内核页表布局变得更加容易,可在 /sys/kernel/debug/kernel_page_tables 中查看。
数组索引边界检查
这是一个涉及 Linux 内核中许多重叠元素(和历史)的较大工作领域。简而言之:C 语言在注意到使用超出声明数组边界的数组索引时表现不佳,我们需要修复这个问题。例如,不要这样做:
|
|
长版本因“灵活数组”结构成员的演变而变得复杂,因此我们暂停一下,简要浏览这个话题。虽然 CONFIG_FORTIFY_SOURCE 等功能试图在 memcpy() 和 strcpy() 系列函数中捕获这些情况,但它无法在开放编码的数组索引中捕获,如上述代码所示。GCC 对这些情况有一个警告(-Warray-bounds),但由于“虚假”灵活数组成员导致的大量误报,Linus 禁用了它。在灵活数组标准化之前,GNU C 支持“零大小”数组成员。在此之前,C 代码会使用 1 元素数组。这些设计都是为了使得某些结构可以作为某些数据块的“头部”,可通过最后一个结构成员寻址:
|
|
将所有零元素和单元素数组成员转换为灵活数组是 Gustavo A. R. Silva 的目标之一,数百个此类更改开始落地。一旦修复,-Warray-bounds 可以重新启用。更多细节可以在内核的弃用文档中找到。
然而,这只会捕获“编译时可见”的情况。对于运行时检查,未定义行为消毒器(Undefined Behavior Sanitizer)有一个选项,用于添加运行时数组边界检查,以捕获编译器无法对索引值进行静态分析的情况:
|
|
然而,直到 Elena Petrova 和我将其拆分为 CONFIG_UBSAN_BOUNDS,它才是独立的(通过内核 Kconfig),该选项足够快,可用于生产内核。启用后,现在可以检测内核以捕获这些条件,这些条件在 Wi-Fi 和蓝牙驱动程序中似乎经常出现。由于 UBSAN(和其他消毒器)默认仅 WARN(),系统所有者如果需要防御针对此类缺陷的攻击,还需要设置 panic_on_warn=1。因此,为了避免内核镜像因所有警告消息而膨胀,我引入了 CONFIG_UBSAN_TRAP,它有效地将这些条件转换为 BUG(),无需额外的 sysctl 设置。
修复“累加”snprintf() 使用
在 C 语言中构建字符串的常见习惯是使用 sprintf() 的返回值递增字符串中的指针,并通过更多 sprintf() 调用构建字符串:
|
|
风险在于,如果这些调用最终超出字符串缓冲区的末尾,它将开始写入其他内存并造成一些糟糕的情况。然而,将这些切换为 snprintf() 并不会使任何东西更安全,因为 snprintf() 返回的是它本应写入的量:
|
|
因此,虽然第一次溢出调用是安全的,但下一次调用将针对数组末尾之后的位置,并且大小计算将回绕为一个巨大的限制。用 scnprintf() 替换此习惯用法解决了问题,因为它仅报告实际写入的量。为此,Takashi Iwai 已经推出了一系列 scnprintf() 修复。
以上就是目前的内容!如果您认为还有其他内容应在此提及,请告诉我。接下来:Linux v5.8。