虚拟映射的内核栈支持 【ChatGPT】
虚拟映射的内核栈支持
作者 Shuah Khan skhan@linuxfoundation.org
概述
这是从引入虚拟映射内核栈功能的代码和原始补丁系列中整理的信息https://lwn.net/Articles/694348/。
介绍
内核栈溢出通常很难调试,并使内核容易受到攻击。问题可能在稍后的时间出现,使得难以隔离和确定根本原因。
带有守护页的虚拟映射内核栈可以立即捕获内核栈溢出,而不会导致难以诊断的破坏。
HAVE_ARCH_VMAP_STACK和VMAP_STACK配置选项启用了带有守护页的虚拟映射栈的支持。此功能在栈溢出时会引发可靠的故障。栈溢出后的堆栈跟踪的可用性以及对溢出本身的响应取决于体系结构。
注意
截至本文撰写时,arm64、powerpc、riscv、s390、um和x86支持VMAP_STACK。
HAVE_ARCH_VMAP_STACK
支持虚拟映射内核栈的体系结构应启用此布尔配置选项。要求如下:
- vmalloc空间必须足够大,以容纳许多内核栈。这可能排除了许多32位体系结构。
- vmalloc空间中的栈需要可靠地工作。例如,如果按需创建vmap页表,则此机制需要在栈指向具有未填充页表的虚拟地址时工作,或者体系结构代码(可能是switch_to()和switch_mm())需要确保在可能未填充的栈上运行之前,栈的页表条目已填充。
- 如果栈溢出到守护页,应该发生一些合理的事情。对“合理”的定义是灵活的,但是立即重新启动而不记录任何内容将是不友好的。
VMAP_STACK
启用VMAP_STACK布尔配置选项时,将分配虚拟映射的任务栈。此选项依赖于HAVE_ARCH_VMAP_STACK。
- 如果要使用带有守护页的虚拟映射内核栈,请启用此选项。这将导致内核栈溢出被立即捕获,而不会导致难以诊断的破坏。
注意
使用此功能与KASAN一起需要体系结构支持,以将虚拟映射与实际影子内存进行支持,并且必须启用KASAN_VMALLOC。
注意
启用VMAP_STACK后,无法在分配的数据上运行DMA。
内核配置选项和依赖关系会不断变化。请参考最新的代码库:
Kconfig https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/Kconfig
分配
创建新的内核线程时,线程栈从页面级分配器的虚拟连续内存页面中分配。这些页面被映射到具有PAGE_KERNEL保护的连续内核虚拟空间中。
alloc_thread_stack_node()调用__vmalloc_node_range()以使用PAGE_KERNEL保护分配栈。
- 分配的栈会被缓存,并由新线程重用,因此在将栈分配/释放给任务时需要手动执行memcg账户。因此,__vmalloc_node_range在没有__GFP_ACCOUNT的情况下调用。
- vm_struct被缓存以便在中断上下文中能够找到当线程释放时的虚拟分配栈。free_thread_stack()可以在中断上下文中调用。
- 在arm64上,所有VMAP的栈需要具有相同的对齐方式,以确保VMAP的栈溢出检测正常工作。特定于体系结构的vmap栈分配器会处理此细节。
- 这不涉及中断栈-根据原始补丁
线程栈分配是从clone()、fork()、vfork()、kernel_thread()通过kernel_clone()开始的。以下是在代码库中搜索以了解何时以及如何分配线程栈的一些提示。
大部分代码位于:kernel/fork.c https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/fork.c。
task_struct中的stack_vm_area指针跟踪着虚拟分配的栈,非空的stack_vm_area指针表示虚拟映射内核栈已启用。
struct vm_struct *stack_vm_area;
栈溢出处理
前导和尾随守护页有助于检测栈溢出。当栈溢出到守护页时,处理程序必须小心,以免再次溢出栈。当调用处理程序时,栈空间可能已经很少。
在x86上,这是通过处理指示内核栈溢出的页故障来完成的,该页故障发生在双重故障栈上。
使用带有守护页的虚拟映射内核栈的测试
我们如何确保VMAP_STACK实际上分配了具有前导和尾随守护页的栈?以下的lkdtm测试可以帮助检测任何回归。
void lkdtm_STACK_GUARD_PAGE_LEADING()
void lkdtm_STACK_GUARD_PAGE_TRAILING()
结论
- 每CPU的vmalloc堆栈缓存似乎比高阶堆栈分配要快一些,至少在缓存命中时是这样。
- THREAD_INFO_IN_TASK完全消除了特定于体系结构的thread_info,并且简单地将thread_info(仅包含标志)和'int cpu'嵌入到task_struct中。
- 任务死亡后,线程栈可以立即释放(无需等待RCU),然后,如果正在使用vmapped栈,则在同一CPU上缓存整个栈以便重用。
本文来自博客园,作者:dolinux,未经同意,禁止转载