162306a36Sopenharmony_ci.. SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci.. include:: ../disclaimer-zh_CN.rst 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci:Original: Documentation/mm/vmalloced-kernel-stacks.rst 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci:翻译: 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci 司延腾 Yanteng Si <siyanteng@loongson.cn> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci:校译: 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci==================== 1362306a36Sopenharmony_ci支持虚拟映射的内核栈 1462306a36Sopenharmony_ci==================== 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci:作者: Shuah Khan <skhan@linuxfoundation.org> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci.. contents:: :local: 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci概览 2162306a36Sopenharmony_ci---- 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci这是介绍 `虚拟映射内核栈功能 <https://lwn.net/Articles/694348/>` 的代码 2462306a36Sopenharmony_ci和原始补丁系列的信息汇总。 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci简介 2762306a36Sopenharmony_ci---- 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci内核堆栈溢出通常难以调试,并使内核容易被(恶意)利用。问题可能在稍后的时间出现,使其难以 3062306a36Sopenharmony_ci隔离和究其根本原因。 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci带有保护页的虚拟映射内核堆栈如果溢出,会被立即捕获,而不会放任其导致难以诊断的损 3362306a36Sopenharmony_ci坏。 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ciHAVE_ARCH_VMAP_STACK和VMAP_STACK配置选项能够支持带有保护页的虚拟映射堆栈。 3662306a36Sopenharmony_ci当堆栈溢出时,这个特性会引发可靠的异常。溢出后堆栈跟踪的可用性以及对溢出本身的 3762306a36Sopenharmony_ci响应取决于架构。 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci.. note:: 4062306a36Sopenharmony_ci 截至本文撰写时, arm64, powerpc, riscv, s390, um, 和 x86 支持VMAP_STACK。 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ciHAVE_ARCH_VMAP_STACK 4362306a36Sopenharmony_ci-------------------- 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci能够支持虚拟映射内核栈的架构应该启用这个bool配置选项。要求是: 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci- vmalloc空间必须大到足以容纳许多内核堆栈。这可能排除了许多32位架构。 4862306a36Sopenharmony_ci- vmalloc空间的堆栈需要可靠地工作。例如,如果vmap页表是按需创建的,当堆栈指向 4962306a36Sopenharmony_ci 具有未填充页表的虚拟地址时,这种机制需要工作,或者架构代码(switch_to()和 5062306a36Sopenharmony_ci switch_mm(),很可能)需要确保堆栈的页表项在可能未填充的堆栈上运行之前已经填 5162306a36Sopenharmony_ci 充。 5262306a36Sopenharmony_ci- 如果堆栈溢出到一个保护页,就应该发生一些合理的事情。“合理”的定义是灵活的,但 5362306a36Sopenharmony_ci 在没有记录任何东西的情况下立即重启是不友好的。 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ciVMAP_STACK 5662306a36Sopenharmony_ci---------- 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ciVMAP_STACK bool配置选项在启用时分配虚拟映射的任务栈。这个选项依赖于 5962306a36Sopenharmony_ciHAVE_ARCH_VMAP_STACK。 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci- 如果你想使用带有保护页的虚拟映射的内核堆栈,请启用该选项。这将导致内核栈溢出 6262306a36Sopenharmony_ci 被立即捕获,而不是难以诊断的损坏。 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci.. note:: 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci 使用KASAN的这个功能需要架构支持用真实的影子内存来支持虚拟映射,并且 6762306a36Sopenharmony_ci 必须启用KASAN_VMALLOC。 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci.. note:: 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci 启用VMAP_STACK时,无法在堆栈分配的数据上运行DMA。 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci内核配置选项和依赖性不断变化。请参考最新的代码库: 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci`Kconfig <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/Kconfig>` 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci分配方法 7862306a36Sopenharmony_ci-------- 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci当一个新的内核线程被创建时,线程堆栈是由页级分配器分配的虚拟连续的内存页组成。这 8162306a36Sopenharmony_ci些页面被映射到有PAGE_KERNEL保护的连续的内核虚拟空间。 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cialloc_thread_stack_node()调用__vmalloc_node_range()来分配带有PAGE_KERNEL 8462306a36Sopenharmony_ci保护的栈。 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci- 分配的堆栈被缓存起来,以后会被新的线程重用,所以在分配/释放堆栈给任务时,要手动 8762306a36Sopenharmony_ci 进行memcg核算。因此,__vmalloc_node_range被调用时没有__GFP_ACCOUNT。 8862306a36Sopenharmony_ci- vm_struct被缓存起来,以便能够找到在中断上下文中启动的空闲线程。 free_thread_stack() 8962306a36Sopenharmony_ci 可以在中断上下文中调用。 9062306a36Sopenharmony_ci- 在arm64上,所有VMAP的堆栈都需要有相同的对齐方式,以确保VMAP的堆栈溢出检测正常 9162306a36Sopenharmony_ci 工作。架构特定的vmap堆栈分配器照顾到了这个细节。 9262306a36Sopenharmony_ci- 这并不涉及中断堆栈--参考原始补丁 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci线程栈分配是由clone()、fork()、vfork()、kernel_thread()通过kernel_clone() 9562306a36Sopenharmony_ci启动的。留点提示在这,以便搜索代码库,了解线程栈何时以及如何分配。 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci大量的代码是在: 9862306a36Sopenharmony_ci`kernel/fork.c <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/fork.c>`. 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_citask_struct中的stack_vm_area指针可以跟踪虚拟分配的堆栈,一个非空的stack_vm_area 10162306a36Sopenharmony_ci指针可以表明虚拟映射的内核堆栈已经启用。 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci:: 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci struct vm_struct *stack_vm_area; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci堆栈溢出处理 10862306a36Sopenharmony_ci------------ 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci前守护页和后守护页有助于检测堆栈溢出。当堆栈溢出到守护页时,处理程序必须小心不要再 11162306a36Sopenharmony_ci次溢出堆栈。当处理程序被调用时,很可能只留下很少的堆栈空间。 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci在x86上,这是通过处理表明内核堆栈溢出的双异常堆栈的缺页异常来实现的。 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci用守护页测试VMAP分配 11662306a36Sopenharmony_ci-------------------- 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci我们如何确保VMAP_STACK在分配时确实有前守护页和后守护页的保护?下面的 lkdtm 测试 11962306a36Sopenharmony_ci可以帮助检测任何回归。 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci:: 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci void lkdtm_STACK_GUARD_PAGE_LEADING() 12462306a36Sopenharmony_ci void lkdtm_STACK_GUARD_PAGE_TRAILING() 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci结论 12762306a36Sopenharmony_ci---- 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci- vmalloced堆栈的percpu缓存似乎比高阶堆栈分配要快一些,至少在缓存命中时是这样。 13062306a36Sopenharmony_ci- THREAD_INFO_IN_TASK完全摆脱了arch-specific thread_info,并简单地将 13162306a36Sopenharmony_ci thread_info(仅包含标志)和'int cpu'嵌入task_struct中。 13262306a36Sopenharmony_ci- 一旦任务死亡,线程栈就可以被释放(无需等待RCU),然后,如果使用vmapped栈,就 13362306a36Sopenharmony_ci 可以将整个栈缓存起来,以便在同一cpu上重复使用。 134