Linux内核v4.15中的安全特性
先前版本:v4.14
Linux内核v4.15于上周发布,其中包含多项我认为有趣的安全改进:
内核页表隔离(KPTI)
PTI已经得到了广泛报道,但总结来说,它主要用于防御CPU缓存时序侧信道攻击,这类攻击可能将内核内存内容暴露给用户空间(CVE-2017-5754,即推测执行的“恶意数据缓存加载”或“Meltdown”漏洞)。
即使仅针对x86_64(作为CONFIG_PAGE_TABLE_ISOLATION),这也是一个巨大的工作量,数月来有许多人参与其中。PowerPC也推出了缓解措施,而arm64(作为CONFIG_UNMAP_KERNEL_AT_EL0)将在v4.16中引入PTI(尽管只有Cortex-A75存在漏洞)。对于使用非常老旧硬件的用户,x86_32版本也在开发中。
x86_64 PTI的另一个好处是,由于现在存在两个页表副本,用户空间映射的内核模式副本可以完全标记为不可执行,这意味着预SMEP硬件现在获得了SMEP模拟。试图跳转到用户空间内存以继续运行恶意代码的内核攻击已经失效(即使攻击者先设法关闭了SMEP)。通过更多工作,还可以引入SMAP模拟(以阻止读取恶意用户空间内存),这将关闭这些常见攻击向量。值得注意的是,arm64自v4.10以来已具备等效功能(PAN模拟)。
retpoline
除了上述PTI工作外,针对CVE-2017-5715(“分支目标注入”或“Spectre变体2”)的retpoline内核缓解措施开始落地。(注意,要获得完整的retpoline支持,您需要打补丁的编译器,如gcc 7.3/8+中出现的版本,目前已在clang中排队等待发布。)
这项工作持续演进,清理工作将持续到v4.16。此外,在v4.16中,我们将开始看到针对其他推测执行变体(即CVE-2017-5753,“边界检查绕过”或“Spectre变体1”)的缓解措施。
x86快速refcount_t溢出保护
在v4.13中,添加了CONFIG_REFCOUNT_FULL代码,以阻止多种类型的引用计数缺陷(性能损失很小)。在v4.14中,基于grsecurity的PAX_REFCOUNT的x86快速仅溢出refcount_t保护基础设施落地,但由于一个错误,在最后一刻被禁用,该错误最终在v4.15中修复。由于这是一个微小的更改,快速refcount_t保护被反向移植并在v4.14.5中为长期维护内核启用。从atomic_t到refcount_t的转换也在继续,现已超过168个,仅剩少数待处理。
%p哈希化
内核信息暴露的许多来源之一是使用%p格式字符串说明符。这些字符串最终出现在各种地方(dmesg、/sys文件、/proc文件等),并且使用分散在整个内核中,这使得修复这种暴露非常困难。早期的努力如kptr_restrict的%pK并没有真正起作用,因为它是选择加入的。虽然最近有一些尝试(由William C Roberts、Greg KH等人)提供切换使%p像%pK一样工作,但Linus最终介入并声明%p应该很少使用,以至于根本不应该使用,Tobin Harding承担了寻找正确前进道路的任务,结果是%p输出使用每启动秘密进行哈希处理。结果是简单的调试继续工作(相同哈希值的两个报告可以确认相同地址,而不说出实际地址是什么),但挫败了攻击者利用此类信息暴露作为攻击构建块的能力。
对于需要未哈希%p的开发人员,引入了%px,但正如Linus警告的那样,要么您的%p在哈希后仍然有用,要么您的%p从一开始就没有实际用处应该被删除,要么您需要强烈证明使用%px具有合理的权限。
我们是否只是将信息暴露问题推迟,五年后我们将与%px和%lx斗争,还有待观察,但希望对此类暴露的态度将发生足够变化,以更好地指导开发人员及其代码。
struct timer_list重构
内核的定时器(struct timer_list)基础设施用于创建在一定时间后执行的回调函数。它们是内核更基础的组成部分之一,因此存在了很长时间,有超过1000个调用点。API随时间改进,但旧的方式一直存在。现代内核回调函数接受一个指向与回调关联结构的参数,以便回调具有上下文,了解哪个回调实例被触发。定时器回调没有这样做,而是接受一个unsigned long,该值被转换回设置定时器的代码希望与回调关联的任何任意上下文,并且该变量与回调的函数指针一起存储在struct timer_list中。这为试图利用内存损坏漏洞(例如堆溢出)的攻击者创造了机会,他们不仅能够覆盖函数指针,还能覆盖存储在内存中的参数。这将攻击提升为弱ROP,并已被用作现代攻击中禁用SMEP的基础(参见retire_blk_timer)。为了消除内核设计中的这一弱点,我重构了定时器回调API及其所有调用者,工作量巨大:
|
|
重构的另一个好处是,一旦内核开始由支持控制流完整性(CFI)的编译器构建,定时器回调不会与所有其他接受单个unsigned long参数的函数混在一起。(换句话说,某些CFI实现不会捕获上述攻击类型,因为攻击者的目标函数仍匹配其原始原型。)
目前就这些;如果我遗漏了什么,请告诉我。v4.16合并窗口现已开放!
© 2018 – 2021, Kees Cook。本作品根据知识共享署名-相同方式共享4.0国际许可协议授权。
评论(3)
3条评论
Hello. 我有一个关于KPTI的问题。对于AMD CPU,启用KPTI是否可取?特别是因为KPTI原本应该更好地保护KASLR,并且用户和内核空间的分离基本上更好,尽管有性能损失。
评论者:Nick — 2018年4月7日 @ 2:48 am
对于没有SMEP的CPU, 我会说无条件保持KPTI开启是值得的。对于SMEP机器,我仍然会保持启用,只是因为它为各种缓存时序攻击(包括许多常见的KASLR泄漏)提供了良好的分离。但最终,我会说这取决于您的工作负载。如果您能处理性能上的微小变化,那就启用吧。
评论者:kees — 2018年4月7日 @ 5:05 am
这也是我最初的想法, 在安全方面不做半途而废的措施。即使AMD CPU默认被排除在KPTI之外。认为还可以假设未来会有优化,进一步遏制KPTI带来的性能损失。感谢快速回复。
评论者:Nick — 2018年4月7日 @ 7:56 am