Linux v4.14 中的安全技术
先前版本:v4.13。
Linux内核v4.14于上周日发布,其中包含多项我认为有趣的安全技术:
ARM64上的虚拟映射内核栈
类似于x86上的相同功能,Mark Rutland和Ard Biesheuvel为ARM64实现了CONFIG_VMAP_STACK
,将内核栈移至隔离且带有保护页的vmap区域。传统栈存在两个主要风险:溢出时覆盖thread_info
结构(包含在copy_to/from_user()
期间检查的addr_limit
字段)以及覆盖相邻栈(或其他分配在栈旁边的对象)。虽然ARM64先前已将thread_info
移出栈以解决前一个问题,但此vmap更改通过vmap保护页的性质增加了最后一道防护。如果内核尝试写入栈末尾之外,它将命中保护页并触发故障。(现在可以通过LKDTM的STACK_GUARD_PAGE_LEADING/TRAILING
测试进行测试。)
保护页防护的一个方面需要进一步关注(在所有架构上):如果栈因栈上的巨型可变长度数组( effectively an implicit alloca()
call)而增长,则可能完全跳过保护页(如用户空间Stack Clash攻击所示)。幸运的是,内核中很少使用VLA。未来,希望我们能见到PaX/grsecurity的STACKLEAK插件,除了其主要目的(在返回到用户空间时清除内核栈)外,它还确保栈扩展不能跳过保护页。这种“栈探测”能力可能也会直接从编译器获得。
set_fs()平衡检查
与上述addr_limit
字段相关,另一类错误是通过不平衡的set_fs()
调用迫使内核意外地将addr_limit
开放给内核内存。在内核的某些区域,为了重用用户空间例程(通常与VFS或compat相关),代码会执行类似操作: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栈限制
继续各种额外防御以保护 against future problems related to userspace memory layout manipulation(如最近在Stack Clash攻击中所示),我在重新处理LSM的secureexec处理后,实现了对特权(即setuid)exec的8MiB栈限制,灵感来自grsecurity中的类似保护。这补充了v4.13中落地的exec参数大小的无条件限制。
randstruct自动结构体选择
虽然从grsecurity移植randstruct gcc插件的大部分工作在v4.13中落地,但启用自动结构体选择所需的最后工作在v4.14中落地。这意味着通过CONFIG_GCC_PLUGIN_RANDSTRUCT
的随机化结构体覆盖现在包括漏洞利用的主要目标之一:函数指针结构体。不知道攻击者需要覆盖的结构体中回调指针的构建随机化位置,漏洞利用变得 much less reliable。
structleak按引用传递变量初始化
Ard Biesheuvel增强了structleak gcc插件,以初始化栈上所有通过引用传递的变量,当使用CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL
构建时。通常,如果变量在使用前未初始化,编译器会发出警告,但如果变量的地址首先传递给函数调用,它会静默此警告,因为它无法判断函数是否确实初始化了内容。因此,插件现在在获取其地址的函数调用之前零初始化此类变量(如果它们尚未初始化)。启用此功能会对性能产生 small impact,但解决了许多栈内容暴露缺陷。(事实上,在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过滤器下运行的子进程生成日志应变得 much easier。
此外,我终于找到了一种方法来实现seccomp的一个经常请求的功能,即杀死整个进程而不仅仅是违规线程。这是通过创建SECCOMP_RET_ACTION_FULL
掩码(原名SECCOMP_RET_ACTION
)并实现SECCOMP_RET_KILL_PROCESS
来完成的。
目前就这些;如果我遗漏了 anything,请告诉我。v4.15合并窗口现已开放!
© 2017 – 2019, Kees Cook。本作品采用知识共享署名-相同方式共享4.0国际许可协议进行许可。