Memset栈空间

在debug期间我们可以给栈空间赋值为一个特定值,比如0x5a,然后在每次中断中检查该值是否发生变化,来检测操作内存附近是否有内存被改写,同时该方法也可以用于统计栈最大使用情况。

-fstack-protector

-fstack-protector 会在函数返回地址之前插入一个保护字(称为“canary”)。如果在函数执行期间发生了缓冲区溢出,可能会覆盖这个保护字。在函数返回之前,编译器会检查这个保护字是否被修改,如果被修改,程序会立即终止,从而防止潜在的攻击。

stack-protector:保护函数中通过alloca()分配缓存以及存在大于8字节的缓存。缺点是保护能力有限。 stack-protector-all:保护所有函数的栈。缺点是增加很多额外栈空间,增加程序体积。 stack-protector-strong:在stack-protector基础上,增加本地数组、指向本地帧栈地址空间保护。 stack-protector-explicit:在stack-protector基础上,增加程序中显式属性"stack_protect"空间。

1
2
3
4
5
6
7
8
9
10
11
12
int add(int val1, int val2) {
return val1 + val2;
}

int main() {
int val1 = 10;
int val2 = 20;

int result = add(val1, val2);

return result;
}

加-fstack-protector-all和不加分别编译后反汇编对比如下:

drbug_stack_overflow1.png

可以看到函数返回前会做一个判断来决定是否返回,如果canary被修改了则执行__stack_chk_fail函数。

guard page

guard page 内核在每个堆栈区域下方放置了一个保护页(即拥有该页的进程无法访问的页)这样当出现栈溢出时就会触发访问没有权限访问的page,内核会panic告知用户程序发生栈溢出问题。guard page不会咱用实际物理内存,只占用虚拟地址空间,对于64bit系统,虚拟地址空间非常大,无需担心这点浪费。

在内核使能了CONFIG_VMAP_STACK选项的情况下会使用vmalloc区域内存作为内核栈空间,这种情况下就可以使用guard page机制了。使能CONFIG_VMAP_STACK带来的一个问题是内核栈地址空间物理地址不再是连续的了,那么就不能使用dma进行数据传输,这个需要注意,使用dma请老实使用kmalloc申请内存

另外thread_info结构体已经被严重弱化了,当前已经不通过该结构体寻找task_struct了,已经嵌入到task_struct结构体内部了。

栈空间

  • 一个函数中的局部变量在栈中的顺序是不定的,是由编译器决定的,故不能通过直接比较两个局部变量的地址来判断栈的增长方向,也不能通过最后定义的变量地址来判断栈指针sp
  • 函数参数压栈的顺序也是不确定的,虽然一般编译器都是从后向前压栈,然而从前向后也不是绝对不可能
  • 连续嵌套调用两个函数时,第一个函数的相关数据会先入栈,之后才是它调用的第二个函数,这一点应该是一个通用成立的结论

Linux下debug

使能stack trace

drbug_stack_overflow2.png

启用stack trace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

mount -t debugfs none /sys/kernel/debug/
cd /sys/kernel/debug/

# 启用内核栈追溯
cat /proc/sys/kernel/stack_tracer_enabled
echo 1 > /proc/sys/kernel/stack_tracer_enabled
cat /proc/sys/kernel/stack_tracer_enabled

# 查看内核栈已用最大值及对应调用链信息
cat /sys/kernel/debug/tracing/stack_max_size
cat /sys/kernel/debug/tracing/stack_trace

# 使用watch动态查看
watch -n 1 'free -m; echo && echo "stack_max_size:"; cat /sys/kernel/debug/tracing/stack_max_size; echo && echo "stack_trace:"; cat /sys/kernel/debug/tracing/stack_trace'

使用function tracer动态追踪内核函数堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看当前内核支持的可追踪的函数
cat /sys/kernel/debug/tracing/available_filter_functions | grep Mbox_invoke

echo Mbox_invoke /sys/kernel/debug/tracing/set_ftrace_filter

# 设置当前追踪为函数追踪
echo function > /sys/kernel/debug/tracing/current_tracer

# 开启函数栈追踪
echo 1 > /sys/kernel/debug/tracing/options/stacktrace

# 查看追踪结果
cat /sys/kernel/debug/tracing/trace

# 注:上述设置步骤可以一条命令搞定:
echo 'Mbox_invoke:stacktrace:10' > /sys/kernel/debug/tracing/set_ftrace_filter

Linux查看当前Thread stack使用情况

1
2
3
4
5
unsigned long *stack = task_stack_page(current);
register unsigned long sp asm ("sp");
printk("stack size: %ld range [%lx %lx] sp : 0x%lx\n", \
(unsigned long)stack, (unsigned long)stack + THREAD_SIZE,sp);

output:

1
stack size: 16384 range [ffffffc0804b0000 ffffffc0804b4000] sp : 0xffffffc0804b3d50