如何在Linux中测试/验证vmalloc保护页是否正常工作
我正在学习Linux中的堆栈保护。我发现Linux内核VMAP_STACKconfig参数正在使用保护页机制和vmalloc()来提供堆栈保护。如何在Linux中测试/验证vmalloc保护页是否正常工作,linux,linux-kernel,stack-overflow,Linux,Linux Kernel,Stack Overflow,我正在学习Linux中的堆栈保护。我发现Linux内核VMAP_STACKconfig参数正在使用保护页机制和vmalloc()来提供堆栈保护。 我试图找到一种方法来检查这个保护页在Linux内核中是如何工作的。我在谷歌上搜索并检查了内核代码,但没有找到代码 另一个问题是如何验证受保护堆栈。 我有一个内核模块使进程的内核堆栈运行不足/溢出,如下所示 static void shoot_kernel_stack(void) { unsigned char *ptr = task_stac
我试图找到一种方法来检查这个保护页在Linux内核中是如何工作的。我在谷歌上搜索并检查了内核代码,但没有找到代码 另一个问题是如何验证受保护堆栈。
我有一个内核模块使进程的内核堆栈运行不足/溢出,如下所示
static void shoot_kernel_stack(void)
{
unsigned char *ptr = task_stack_page(current);
unsigned char *tmp = NULL;
tmp = ptr + THREAD_SIZE + PAGE_SIZE + 0;
// tmp -= 0x100;
memset(tmp, 0xB4, 0x10); // Underrun
}
我真的感到内核恐慌,如下所示
[ 8006.358354] BUG: stack guard page was hit at 00000000e8dc2d98 (stack is 00000000cff0f921..00000000653b24a9)
[ 8006.361276] kernel stack overflow (page fault): 0000 [#1] SMP PTI
这是验证保护页的正确方法吗?使用VMAP\u堆栈Linux功能将线程的内核堆栈映射到VMA。通过虚拟映射堆栈,底层物理页不需要是连续的。可以通过添加保护页来检测跨页溢出。由于VMA后面紧跟着一个保护(除非在分配时传递了VM_NO_guard标志),因此在这些区域中分配的堆栈在堆栈溢出检测方面受益匪浅 分配 线程堆栈在线程创建时使用kernel/fork.c中的alloc_thread_stack_node()进行分配。当激活VMAP_堆栈时,因为根据源代码中的注释: vmalloc()有点慢,调用vfree()足够多的次数将强制TLB 脸红尝试通过缓存堆栈来最小化调用数量 内核堆栈大小为线程大小(在x86_64平台上等于4页)。在线程创建时调用的分配的源代码是:
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
#ifdef CONFIG_VMAP_STACK
void *stack;
int i;
[...] // <----- Part which gets a previously cached stack. If no stack in cache
// the following is run to allocate a brand new stack:
/*
* Allocated stacks are cached and later reused by new threads,
* so memcg accounting is performed manually on assigning/releasing
* stacks to tasks. Drop __GFP_ACCOUNT.
*/
stack = __vmalloc_node_range(THREAD_SIZE, THREAD_ALIGN,
VMALLOC_START, VMALLOC_END,
THREADINFO_GFP & ~__GFP_ACCOUNT,
PAGE_KERNEL,
0, node, __builtin_return_address(0));
[...]
在上面的代码中,当检测到/overflow下的堆栈时,将调用arch/x86/kernel/traps.c)中定义的handle_stack_overflow()函数:
#ifdef CONFIG_VMAP_堆栈
__可见void\uuu noreturn句柄\u堆栈\u溢出(const char*消息,
结构pt_regs*regs,
无符号长错误(U地址)
{
printk(KERN_EMERG“错误:堆栈保护页在%p处被击中(堆栈为%p..%p)\n”,
(void*)故障地址,当前->堆栈,
(字符*)当前->堆栈+线程大小-1);
骰子(消息,regs,0);
/*绝对确定我们不会回来*/
恐慌(“%s”,消息);
}
#恩迪夫
问题中指出的示例错误消息“BUG:stack guard page hat at…”来自上面的handle_stack_overflow()函数
来自您的示例模块
定义VMAP\u STACK时,将显示任务描述符的STACK\u vm\u area字段,并使用与堆栈关联的VMA地址进行设置。从那里,可以获取有趣的信息:
struct task\u struct*task;
#ifdef CONFIG_VMAP_堆栈
结构vm_结构*vm;
#endif//CONFIG_VMAP_堆栈
任务=当前任务;
printk(“\tKernel stack:0x%lx\n”,(无符号长)(任务->堆栈));
printk(“\tStack-end-magic:0x%lx\n”,*(无符号长*)(任务->堆栈));
#ifdef CONFIG_VMAP_堆栈
vm=任务->堆栈\虚拟机\区域;
printk(“\tstack\u vm\u area->addr=0x%lx\n”,(无符号长)(vm->addr));
printk(“\tstack\u vm\u区域->nr\u页面=%u\n”,vm->nr\u页面);
printk(“\tstack\u vm\u area->size=%lu\n”,vm->size);
#endif//CONFIG_VMAP_堆栈
printk(“\t堆栈中的局部变量:0x%lx\n”,(无符号长)(&task));
nr_pages字段是没有附加保护页的页数。堆栈顶部的最后一个无符号长字符由include/uapi/linux/MAGIC.h中定义的stack\u END\u MAGIC设置为:
参考资料:
static struct vm_struct *__get_vm_area_node(unsigned long size,
unsigned long align, unsigned long flags, unsigned long start,
unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
struct vmap_area *va;
struct vm_struct *area;
BUG_ON(in_interrupt());
size = PAGE_ALIGN(size);
if (unlikely(!size))
return NULL;
if (flags & VM_IOREMAP)
align = 1ul << clamp_t(int, get_count_order_long(size),
PAGE_SHIFT, IOREMAP_MAX_ORDER);
area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
if (unlikely(!area))
return NULL;
if (!(flags & VM_NO_GUARD)) // <----- A GUARD PAGE IS ADDED
size += PAGE_SIZE;
va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
if (IS_ERR(va)) {
kfree(area);
return NULL;
}
setup_vmalloc_vm(area, va, flags, caller);
return area;
}
#define STACK_END_MAGIC 0x57AC6E9D