一、堆栈扩展

在进程创建的时候,内核并没有为进程分配太多的堆栈,即使是逻辑地址空间也没有,这样做的好处就是如果说用户态的程序堆栈向下溢出(对386来说,就是访问了更低地址的内存空间),这样内核可以比较容易的检测出这种错误,尽管这种错误出现的可能性要比向上溢出的概率小的多。记得在之前使用VS编译器的时候,编译器还有一个堆栈探测过程,就是对于局部变量大小超过一个页面的函数,编译器会生成额外的probe指令来预先来踩一脚这些页面,可能是windows内核中只允许一次向下扩展一个页面的堆栈空间?不管如何,这个我们就不验证了,因为我现在没有装windows的编译器啊,所以只能看这个Linux下的这种实现了。

二、内核中判断

linux-2.6.21\mm\mmap.c文件中的

int expand_stack(struct vm_area_struct *vma, unsigned long address)

函数负责对堆栈进行扩展,这个名字贴切而拉轰,以至于我一眼就就找到了它。在其中访问地址和堆栈的判断,其中对于限制的代码并不多,大致来说是集中在

acct_stack_growth

函数中。这个函数中并没有检测一个页面限制,所以在这个里面是没有对向下溢出多少发生错误提供信息。

三、真正判断位置

事实上,这个是一个硬件相关的一个特殊判断

1、386体系结构实现

对于我们常见的386来说,其实现位于linux-2.6.21\arch\i386\mm\fault.c

fastcall void __kprobes do_page_fault(struct pt_regs *regs,

unsigned long error_code)

if (error_code & 4) {

/*

* Accessing the stack below %esp is always a bug.

* The large cushion allows instructions like enter

* and pusha to work.  ("enter $65535,$31" pushes

* 32 pointers and then decrements %esp by 65535.)

*/

if (address + 65536 + 32 * sizeof(unsigned long) < regs->esp)

goto bad_area;

}

也就是这里判断是如果访问地址在386栈顶位置向下32 * sizeof(unsigned long),则认为是越界访问,当然,这里处理也是有些简单粗暴了,因为这里更加详细的做法应该是判断一下指令,这里所说的指令应该在大部分时间都是不用的,所以这个判断应该相对是一个过于宽泛的限制,这个65536相当于16个大小为4KB的页面,所以还是比较宽泛的。

2、powerpc实现

我们再看一下powepc的处理

/*

* N.B. The POWER/Open ABI allows programs to access up to

* 288 bytes below the stack pointer.

* The kernel signal delivery code writes up to about 1.5kB

* below the stack pointer (r1) before decrementing it.

* The exec code can write slightly over 640kB to the stack

* before setting the user r1.  Thus we allow the stack to

* expand to 1MB without further checks.

*/

if (address + 0x100000 < vma->vm_end) {对于堆栈空间刚开始的1M空间之下的内容,可以随意扩展而不加检测。

/* get user regs even if this fault is in kernel mode */

struct pt_regs *uregs = current->thread.regs;

if (uregs == NULL)

goto bad_area;

/*

* A user-mode access to an address a long way below

* the stack pointer is only valid if the instruction

* is one which would update the stack pointer to the

* address accessed if the instruction completed,

* i.e. either stwu rs,n(r1) or stwux rs,r1,rb

* (or the byte, halfword, float or double forms).

*

* If we don‘t check this then any write to the area

* between the last mapped region and the stack will

* expand the stack rather than segfaulting.

*/

if (address + 2048 < uregs->gpr[1]

&& (!user_mode(regs) || !store_updates_sp(regs)))如果堆栈已经扩展到1M一下,这里检测开始加强,只能访问2048字节之下范围,否则这里真的判断了特殊指令,所以这个应该比较准确,可能是由于RISC机型的指令操作比较简单?

goto bad_area;

}

四、386系统调用时寄存器使用情况和这个判断的关系

386处理器有8个通用寄存器 E[ABCD]X,E[SD]I,E[SB]P,这八个寄存器,但是现在的系统调用使用寄存器传递的话,可以看到386系统中最多只是用了7个寄存器,这里唯一没有使用的就是ESP寄存器,通过这里我们可以猜测,如果用户态把ESP也作为寄存器传递入内核的话,那么内核在栈顶判断的时候这里可能就不准确,因为用户态的代码是可能缺页的。

五、验证一下

[[email protected] stackflow]$ cat stackflow.c

#include

int overflower()

{

//char placeholder[0x1000*19];

char localvar,*localvaraddr=&localvar,page;

int myesp;

__asm__ (

"movl %%esp,%0"

:"=r"(myesp)); //这里使用汇编语言获得栈顶指针ESP。

printf("myesp is %#x,most acc %#x\n",myesp,myesp-65536-32*sizeof(unsigned long));//模拟内核的计算规则,算出可以访问的最低位置

for(page =0 ; ;page++)//以页面为单位访问ESP之下内存,看何时触发内核段错误。

{

printf("probing %#x\n",localvaraddr-(page<<12));

localvaraddr[-(page<<12)] = 0;

}

return 0;

}

int main()

{

return overflower();

}

[[email protected] stackflow]$ gcc  stackflow.c -g -o stackflow.c.exe -static

[[email protected] stackflow]$ ./stackflow.c.exe

myesp is 0xbfb45c10,most acc 0xbfb35b90

probing 0xbfb45c23

probing 0xbfb44c23

probing 0xbfb43c23

probing 0xbfb42c23

probing 0xbfb41c23

probing 0xbfb40c23

probing 0xbfb3fc23

probing 0xbfb3ec23

probing 0xbfb3dc23

probing 0xbfb3cc23

probing 0xbfb3bc23

probing 0xbfb3ac23

probing 0xbfb39c23

probing 0xbfb38c23

probing 0xbfb37c23

probing 0xbfb36c23

probing 0xbfb35c23

probing 0xbfb34c23根据计算,这个地址是不能访问的,但是这里并没有触发异常。

probing 0xbfb33c23

probing 0xbfb32c23

probing 0xbfb31c23这里总共访问了大致21个页面,与我们假设内容不同。

Segmentation fault (core dumped)

[[email protected] stackflow]$

六、现象分析(进程初始化堆栈空间多大)

这一点要说到内核为一个可执行文件建立一个堆栈的时候,这个堆栈空间是多大,也就是内核为一个进程启动的过程中一次性分配了多少vma区间。这个问题在内核的

linux-2.6.21\fs\exec.c:setup_arg_pages

函数中有决定性影响:

arg_size += EXTRA_STACK_VM_PAGES * PAGE_SIZE;

……

#ifdef CONFIG_STACK_GROWSUP

mpnt->vm_start = stack_base;

mpnt->vm_end = stack_base + arg_size;

#else

mpnt->vm_end = stack_top;

mpnt->vm_start = mpnt->vm_end - arg_size;

#endif

其中

#define EXTRA_STACK_VM_PAGES    20    /* random */

也就是说,内核在为一个进程分配堆栈的时候,将会在实际使用的堆栈基础之上在额外增加20个页面的虚拟地址空间。由于实际上一个进程真正使用的参数空间一般小于一个页面(4KB),所以通常一个进程最开始堆栈分配的空间为21个页面,我们随便找一个程序看一下:

[[email protected] stackflow]$ cat /proc/self/maps

0017b000-0017c000 r-xp 00000000 00:00 0          [vdso]

001e8000-00206000 r-xp 00000000 fd:00 1280       /lib/ld-2.11.2.so

00206000-00207000 r--p 0001d000 fd:00 1280       /lib/ld-2.11.2.so

00207000-00208000 rw-p 0001e000 fd:00 1280       /lib/ld-2.11.2.so

0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so

0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so

0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so

0037f000-00380000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so

00380000-00383000 rw-p 00000000 00:00 0

08048000-08053000 r-xp 00000000 fd:00 68967      /bin/cat

08053000-08055000 rw-p 0000a000 fd:00 68967      /bin/cat

09af0000-09b11000 rw-p 00000000 00:00 0          [heap]

b75b8000-b77b8000 r--p 00000000 fd:00 100518     /usr/lib/locale/locale-archive

b77b8000-b77b9000 rw-p 00000000 00:00 0

b77ce000-b77cf000 rw-p 00000000 00:00 0

bfc1f000-bfc34000 rw-p 00000000 00:00 0          [stack]这个空间也是21个页面,同一个系统中多次执行这个命令,栈顶的位置并不确定,这是因为load_elf_binary在调用setup_arg_pages的时候执行了randomize_stack_top,所以这个堆栈区间不确定,但是大小确定。

回到我们刚才说的那个问题,由于堆栈向下的20个页面都是在初始堆栈的虚拟地址空间中的,所以它不会触发堆栈扩展检测,因为这个本来已经在堆栈区间中了。

七、再次模拟

把overflower函数中的

//char placeholder[0x1000*19];

注释打开,从而让堆栈尽可能多的占用更多的堆栈空间,也就是迫使esp濒临原始堆栈vma区间下边界,然后开始逐步探测,此时计算结果会达到预期结果

[[email protected] stackflow]$ ./stackflow.c.exe

myesp is 0xbf9f4b40,most acc0xbf9e4ac0

probing 0xbf9f4b53

probing 0xbf9f3b53

probing 0xbf9f2b53

probing 0xbf9f1b53

probing 0xbf9f0b53

probing 0xbf9efb53

probing 0xbf9eeb53

probing 0xbf9edb53

probing 0xbf9ecb53

probing 0xbf9ebb53

probing 0xbf9eab53

probing 0xbf9e9b53

probing 0xbf9e8b53

probing 0xbf9e7b53

probing 0xbf9e6b53

probing 0xbf9e5b53

probing 0xbf9e4b53

probing 0xbf9e3b53访问出错位置在预测位置之下。

Segmentation fault (core dumped)

linux扩展堆函数,linux下进程堆栈下溢出判断及扩展实现相关推荐

  1. linux的scandir函数,linux C++ scandir 的使用

    () 头文件 #include () 函数定义 int scandir(const char *dir,struct dirent **namelist,int (*filter)(const voi ...

  2. linux c 绝对值函数,linux c 简介

    --- title: linux c 简介 date: 2020-07-19 updated: 2020-07-19 --- # Preface # todo linux c http://blog. ...

  3. linux使用模板函数,Linux常用C函数

    Linux常用C函数Tag内容描述: 1.Linux常用C函数 接口处理篇 accept(接受socket连线) 相关函数 socket,bind,listen,connect 表头文件 #inclu ...

  4. linux的poll_wait函数,Linux poll机制详细讲解

    所有的系统调用,基于都可以在它的名字前加上"sys_"前缀,这就是它在内核中对应的函数.比如系统调用open.read.write.poll,与之对应的内核函数为:sys_open ...

  5. linux 文件操作函数,Linux下的文件操作函数及creat用法

    编写Linux应用程序要用到如下工具: (1)编译器:GCC GCC是Linux平台下最重要的开发工具,它是GNU的C和C++编译器,其基本用法为:gcc [options] [filenames]. ...

  6. linux拷贝文件函数,linux下文件操作的各个函数

    作者:HoytEmail:hoytluo@21cn.com 前言: 我们在这一节将要讨论linux下文件操作的各个函数. 文件的创建和读写 文件的各个属性 目录文件的操作 管道文件 --------- ...

  7. linux i2c 读写函数,Linux下读写芯片的I2C寄存器

    要想在Linux下读写芯片的I2C寄存器,一般需要在Linux编写一份该芯片的I2C驱动,关于Linux下如何编写I2C驱动,前一篇文章<手把手教你写Linux I2C设备驱动>已经做了初 ...

  8. linux中iconv函数,Linux下编码转换(iconv函数族)

    转自:http://www.linuxdiyf.com/viewarticle.php?id=45164 在Linux上进行编码转换时,既可以利用iconv函数族编程实现,也可以利用iconv命令来实 ...

  9. linux的gets函数,Linux 下使用C语言 gets()函数报错

    在Linux下,使用 gets(cmd) 函数报错:warning: the 'gets' function is dangerous and should not be used. 解决办法:采用 ...

最新文章

  1. 我是一名黑客我也是一名程序员
  2. 组合模式_[设计模式]10.组合模式
  3. 【已解决】请先调用 init 完成初始化后再调用其他云 API。init 方法可传入一个对象用于设置默认配置,详见文档。; at cloud.callFunction api 解决方案
  4. 微信实时Look-alike算法分享赏析
  5. 改变libreOffice的Calc的背景颜色
  6. IBM并购网络视频会议商WebDialogs 加入Lotus Sametime
  7. 【AudioVideo】处理音频输出的变化(13)
  8. 无辜程序员被新女同事算计,老板到底安得什么心 ?
  9. 2.2-特殊权限之sticky
  10. 机器学习项目实战----信用卡欺诈检测(二)
  11. 计算机二级考试能报考的科目,计算机二级考试有哪些科目可以选择报考?
  12. 2022牛客寒假算法基础集训营2 签到题7题
  13. android实现地图功能实现,Android百度地图应用之基本地图功能实现
  14. 数字集成电路设计之加法器
  15. c51单片机汇编语言指令,51单片机汇编指令详解
  16. GoogleEarth的安装与使用
  17. 员工管理系统-SpringBoot+Vue入门小项目实战
  18. eccv 2018 image caption generation论文导读
  19. SQL语句中where 1=0是什么意思
  20. 从开发转到安全渗透工程师,是我做的最对的决定

热门文章

  1. [开源工具]小鸟云虚拟主机+wordpress搭建个人博客(不喜勿喷)
  2. 请不要问LCD和LED屏哪个好了,因为这完全不是同一个概念!
  3. windows 10 下运行 docker desktop 报错 cannot enable hyper-v service
  4. 【陈曲写作】考研英语写作
  5. 怒写400篇AI文章!这群妹子卷疯了…
  6. 公司金融01.现值与贴现
  7. 改变无数人命运的上证指数
  8. 线路负载及故障检测装置《我搜集的资料》
  9. 如何在html网页添加动态特效,《让网页动起来----动态HTML》教学案例
  10. 七月在线笔记之推荐系统