Linux v4.14 内核安全特性深度解析

本文详细介绍了Linux内核v4.14版本中的多项安全增强特性,包括虚拟映射内核栈、set_fs()平衡检查、SLUB分配器加固、setuid-exec栈限制、结构体随机化、栈泄露防护、启动熵改进、eBPF JIT支持及seccomp优化等。

Linux v4.14 安全特性详解

Linux内核v4.14于2017年11月发布,引入了多项重要的安全增强功能,以下是其中一些值得关注的内容:

arm64上的虚拟映射内核栈

与x86架构类似,Mark Rutland和Ard Biesheuvel为arm64实现了CONFIG_VMAP_STACK,将内核栈移至隔离且带有保护页的虚拟映射区域。传统栈存在两个主要风险:溢出覆盖thread_info结构(包含在copy_to/from_user()中检查的addr_limit字段)和覆盖相邻栈(或其他分配在栈附近的内容)。虽然arm64此前已将thread_info移出栈以解决前一个问题,但此次虚拟映射变更通过保护页的特性增加了最后一层防护。如果内核尝试写入栈末尾之外,将触及保护页并触发故障。(现在可通过LKDTM的STACK_GUARD_PAGE_LEADING/TRAILING测试进行验证。)

保护页防护的一个方面需要进一步关注(在所有架构上):如果栈因巨大的可变长度数组(VLA)而增长(相当于隐式的alloca()调用),可能完全跳过保护页(如用户空间的Stack Clash攻击所示)。幸运的是,内核中很少使用VLA。未来,希望看到PaX/grsecurity的STACKLEAK插件被加入,该插件除了主要目的是在返回到用户空间时清除内核栈外,还确保栈扩展不能跳过保护页。这种“栈探测”能力可能也会直接由编译器提供。

set_fs()平衡检查

与上述addr_limit字段相关,另一类漏洞是通过不平衡的set_fs()调用迫使内核意外地将addr_limit开放给内核内存。在内核的某些区域,为了重用用户空间例程(通常与VFS或兼容性相关),代码会执行类似操作:set_fs(KERNEL_DS); ...某些代码...; set_fs(USER_DS);。当USER_DS调用缺失时(通常由于错误的错误路径或异常),后续系统调用可能突然开始通过copy_to_user写入内核内存(其中“to user”实际上意味着“在addr_limit范围内”)。

Thomas Garnier为x86、arm和arm64实现了系统调用退出时的USER_DS检查(带有一个小修复)。这意味着损坏的set_fs()设置不会扩展到未能将其设置回USER_DS的错误系统调用之外。此外,作为讨论处理此功能最佳方式的一部分,Christoph Hellwig和Al Viro(及其他人员)已进行了大量更改,以避免完全使用set_fs(),这应大大减少未来可能引入此类漏洞的地方。

SLUB空闲列表加固

一类常见的堆攻击是覆盖存储在未分配的SLUB缓存对象中的空闲列表指针。PaX/grsecurity开发了一种廉价的防御措施,通过使用全局随机值(和存储地址)对空闲列表指针进行XOR操作。Daniel Micay通过使用每个缓存的随机值改进了这一点,我进一步重构了代码。最终的功能,通过CONFIG_SLAB_FREELIST_HARDENED启用,使得空闲列表指针覆盖非常难以利用,除非攻击者找到了暴露随机值和指针位置的方法。这应使盲堆溢出漏洞的利用难度大大增加。

此外,Alexander Popov实现了一个简单的双重释放防御,类似于GNU C库中的“fasttop”检查,它将捕获同一指针的顺序free()调用。(并且已经发现了一个漏洞。)

未来的工作是为SLAB分配器提供类似的元数据保护(尽管SLAB不将其空闲列表存储在单个未使用的对象中,因此与SLUB相比,它有一组不同的暴露点)。

setuid-exec栈限制

继各种额外防御措施之后,以保护免受用户空间内存布局操作相关的未来问题(如最近的Stack Clash攻击所示),我在重新处理LSM的secureexec处理之后,实现了对特权(即setuid)exec的8MiB栈限制,灵感来自grsecurity中的类似保护。这补充了v4.13中引入的对exec参数大小的无条件限制。

randstruct自动结构选择

虽然从grsecurity移植randstruct gcc插件的大部分工作在v4.13中完成,但启用自动结构选择所需的最后工作是在v4.14中完成的。这意味着通过CONFIG_GCC_PLUGIN_RANDSTRUCT的随机化结构覆盖现在包括漏洞利用的主要目标之一:函数指针结构。如果不知道攻击者需要覆盖的结构中回调指针的构建随机化位置,漏洞利用的可靠性将大大降低。

structleak按引用传递变量初始化

Ard Biesheuvel增强了structleak gcc插件,以初始化栈上所有通过引用传递的变量,当使用CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL构建时。通常,如果变量在使用前未初始化,编译器会发出警告,但如果变量的地址首先传递给函数调用,它会沉默此警告,因为它无法判断函数是否确实初始化了内容。因此,插件现在在获取地址的函数调用之前零初始化此类变量(如果它们尚未初始化)。启用此功能会对性能产生较小影响,但解决了许多栈内容暴露缺陷。(事实上,在v4.15开发周期中报告的至少一个此类缺陷已由此插件缓解。)

改进的启动熵

Laura Abbott和Daniel Micay通过将栈保护器设置推迟到启动后期,并在启动熵收集中包括内核命令行,改进了栈保护器可用的早期启动熵(因为某些设备每次启动时都会更改)。

32位ARM的eBPF JIT

ARM BPF JIT已经存在一段时间,但它不支持eBPF(因此,不提供常数值盲化,这意味着它暴露于被攻击者使用BPF常数值构建任意机器代码)。Shubham Bansal花了大量时间构建了一个完整的32位ARM eBPF JIT,既加速了eBPF,又使其与内核中的JIT漏洞防御保持同步。

seccomp改进

Tyler Hicks解决了seccomp如何记录操作结果的一个长期缺陷。除了创建一种方法来标记特定的seccomp过滤器需要通过SECCOMP_FILTER_FLAG_LOG进行记录外,他还添加了一个新的操作结果SECCOMP_RET_LOG。有了这些更改,开发人员检查seccomp过滤器结果以及进程启动器为其在seccomp过滤器下运行的子进程生成日志应该容易得多。

此外,我终于找到了一种方法来实现seccomp的一个经常请求的功能,即杀死整个进程而不仅仅是违规线程。这是通过创建SECCOMP_RET_ACTION_FULL掩码(原名SECCOMP_RET_ACTION)并实现SECCOMP_RET_KILL_PROCESS来完成的。

以上就是目前的内容;如果我遗漏了什么,请告诉我。v4.15合并窗口现已开放!

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