栈溢出
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 | int add(int val1, int val2) { |
加-fstack-protector-all和不加分别编译后反汇编对比如下:
可以看到函数返回前会做一个判断来决定是否返回,如果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
启用stack trace
1 |
|
使用function tracer动态追踪内核函数堆栈
1 | # 查看当前内核支持的可追踪的函数 |
Linux查看当前Thread stack使用情况
1 | unsigned long *stack = task_stack_page(current); |
output:
1 | stack size: 16384 range [ffffffc0804b0000 ffffffc0804b4000] sp : 0xffffffc0804b3d50 |