`

一个栈溢出的BUG

阅读更多
原帖地址:http://www.cnblogs.com/sheniudou/archive/2013/06/14/3135142.html<style><!--<br/>/*--><![CDATA[/*><!--*/<br/> html { font-family: Times, serif; font-size: 12pt; }<br/> .title { text-align: center; }<br/> .todo { color: red; }<br/> .done { color: green; }<br/> .tag { background-color: #add8e6; font-weight:normal }<br/> .target { }<br/> .timestamp { color: #bebebe; }<br/> .timestamp-kwd { color: #5f9ea0; }<br/> .right {margin-left:auto; margin-right:0px; text-align:right;}<br/> .left {margin-left:0px; margin-right:auto; text-align:left;}<br/> .center {margin-left:auto; margin-right:auto; text-align:center;}<br/> p.verse { margin-left: 3% }<br/> pre {<br/>border: 1pt solid #AEBDCC;<br/>background-color: #F3F5F7;<br/>padding: 5pt;<br/>font-family: courier, monospace;<br/> font-size: 90%;<br/> overflow:auto;<br/> }<br/> table { border-collapse: collapse; }<br/> td, th { vertical-align: top; }<br/> th.right { text-align:center; }<br/> th.left { text-align:center; }<br/> th.center { text-align:center; }<br/> td.right { text-align:right; }<br/> td.left { text-align:left; }<br/> td.center { text-align:center; }<br/> dt { font-weight: bold; }<br/> div.figure { padding: 0.5em; }<br/> div.figure p { text-align: center; }<br/> div.inlinetask {<br/> padding:10px;<br/> border:2px solid gray;<br/> margin:10px;<br/> background: #ffffcc;<br/> }<br/> textarea { overflow-x: auto; }<br/> .linenr { font-size:smaller }<br/> .code-highlighted {background-color:#ffff00;}<br/> .org-info-js_info-navigation { border-style:none; }<br/> #org-info-js_console-label { font-size:10px; font-weight:bold;<br/> white-space:nowrap; }<br/> .org-info-js_search-highlight {background-color:#ffff00; color:#000000;<br/> font-weight:bold; }<br/>--></style>
<style><!--<br/>/*--><![CDATA[/*><!--*/<br/> pre{background-color: #817A7B;}<br/> img{border: 1px solid gray;}<br/>--></style>


<script type="text/javascript">// <![CDATA[<br/>/*--><![CDATA[/*><!--*/<br/> function CodeHighlightOn(elem, id)<br/> {<br/> var target = document.getElementById(id);<br/> if(null != target) {<br/> elem.cacheClassElem = elem.className;<br/> elem.cacheClassTarget = target.className;<br/> target.className = "code-highlighted";<br/> elem.className = "code-highlighted";<br/> }<br/> }<br/> function CodeHighlightOff(elem, id)<br/> {<br/> var target = document.getElementById(id);<br/> if(elem.cacheClassElem)<br/> elem.className = elem.cacheClassElem;<br/> if(elem.cacheClassTarget)<br/> target.className = elem.cacheClassTarget;<br/> }<br/>/*]]>*/<br/>// ]]></script>



我的博客:http://blog.striveforfreedom.net



Table of Contents





1 BUG描述



最近修改一C程序,在一个结构体里加入了几个新的字段,编译完一跑竟然出现段错误(segmentation fault)崩溃了。用gdb查看,引发崩溃的是一条这样的指令:mov register offset(%rsp)。





2 解决过程



从引发崩溃的指令可以看出,崩溃的原因是访问了栈上的内存,然而通常来说访问栈上内存是不会导致段错误的,因为栈上内存不需要程序员手动管理,一般来说很难出错。猜测有可能是栈溢出了,需要证实这个想法。发生崩溃的机器是X86_64+Linux,用ulimit -s得知进程栈默认的soft limit是10MB,因为程序代码并没有调用setrlimit调整过栈的soft limit,于是需要证明出现段错误的进程栈大于10MB了,导致崩溃的地址可以从gdb中查看,如果知道栈的起始地址(栈底),两者之差就是栈的大小。但如何才能知道栈的起始地址呢?我们知道Linux有个proc文件系统,系统里每个进程在/proc下都有一个以进程ID命名的文件夹,/proc/PID下包含的都是进程ID为PID的进程的相关信息,比如说该进程对应的可执行文件路径/当前目录/打开的文件等。其中/proc/PID/maps包含了该进程所有虚拟区域的起始和结束地址,包括栈(即文件maps里最后一个字段为[stack]对应的那一行)。拿到了栈的起始地址之后,用起始地址减去引发崩溃的那条指令中访问的内存地址(即rsp加一个偏移),得到的值果然大于10MB了。至于栈溢出的原因,原有代码在栈上定义了一个结构体的数组,而我在结构体里面加了几个size比较大的字段,因此溢出了。找到原因,BUG修改起来就很简单了,要么在shell里修改栈默认的soft limit,要么在代码里调用setrlimit,要么在堆上分配内存。


为了说明这个BUG,我写一段测试代码作为例子,代码如下:


int main(int argc, char* argv[])
{
const unsigned len = 10 * (1U << 20);
char data[len];
data[0] = 'a';

return 0;
}

编译完一运行,出乎意料的是,进程竟然没崩溃!这就非常奇怪了,因为我在main函数里定义了10MB大小的数组(并且访问了第一个元素,即最地址最小的那个),且不说环境变量所占空间,单这个数组加上C运行库调用序列所占空间就超过10MB了,而栈soft limit是10MB,按理说必然崩溃。然而实际却没有崩溃,一开始我怀疑代码被优化掉了,用objdump一看并没有优化掉,接着想了很久都没有头绪,最后终于想起去查看内核代码,看看内核到底是怎么处理栈溢出的。这包含两个方面,一是栈的soft limit是怎么读取出来的,二是内核怎么检查栈大小是否超过soft limit了。发生崩溃的机器上装的是CentOS 5.7,用uname -r得到的内核版本是2.6.18-308.el5,这个版本号跟官方内核版本号对应不上,因为感觉应该很接近2.6.18,于是就查看了官方内核2.6.18的代码(推荐下 lxr.linux.no ,查看某个版本内核代码很方便,不用去下载几十M的源码包了)。


读取资源限制(soft limit & hard limit)是由系统调用getrlimit完成的,getrlimit在内核中的入口是sys_getrlimit,代码如下:


asmlinkage long sys_getrlimit(unsigned int resource, struct rlimit __user *rlim)
{
if (resource >= RLIM_NLIMITS)
return -EINVAL;
else {
struct rlimit value;
task_lock(current->group_leader);
value = current->signal->rlim[resource];
task_unlock(current->group_leader);
return copy_to_user(rlim, &value, sizeof(*rlim)) ? -EFAULT : 0;
}
}

这个函数很简单,就是把当前进程的某种资源限制读出来,并复制到用户空间,没有发现什么问题。


对栈的大小限制的检查是在页面异常(page fault)处理中完成的,从页面异常入口page_fault开始,查看调用序列page_fault > do_page_fault > expand_stack > acct_stack_growth,在函数acct_stack_growth中发现了对栈大小限制进行检查的代码,如下(省略了跟我们这个例子无关的代码):


static int acct_stack_growth(struct vm_area_struct * vma, unsigned long size, unsigned long grow)
{
//...
struct rlimit *rlim = current->signal->rlim;
//...

/* Stack limit test */
if (size > rlim[RLIMIT_STACK].rlim_cur)
return -ENOMEM;

//...
}

其中,参数size是栈的起始地址减去当前引发页面异常的地址并按页大小向上对齐的,很显然,这里如果发现栈大小大于soft limit就返回错误,最终会给当前进程发送SIGSEGV信号,导致进程出现段错误崩溃。上面的内核代码说明我的想法是正确的,然而进程并未和我预料的那样崩溃,一想可能是内核版本不对,于是又查看了官方2.6.19版的代码,发现这两处的代码并没有改过。这就很奇怪了,过了一会我突然想到,机器上装的是CentOS,CentOS可能修改了这处的官方内核代码,于是我下载了和我系统对应的源码包kernel-2.6.18-308.el5.src.rpm,安装之后,发现该版本对应的官方内核版本是2.6.18.4,CentOS的修改过的代码放在一个patch文件kernel-2.6.18-redhat.patch里,运行patch之后,果然发现CentOS修改了acct_stack_growth函数,修改如下(该函数有多处修改,这里只列出了跟我们这个BUG相关的修改):


static int acct_stack_growth(struct vm_area_struct * vma, unsigned long size, unsigned long grow)
{
//...
struct rlimit *rlim = current->signal->rlim;
//...

/* Stack limit test */
if (over_stack_limit(size))
return -ENOMEM;

//...
}

一比较就可以发现官方内核代码是直接比较size和栈的soft limit,而CentOS把这个比较放进了函数over_stack_limit里,再来看函数over_stack_limit:


static int over_stack_limit(unsigned long sz)
{
if (sz < EXEC_STACK_BIAS)
return 0;
return (sz - EXEC_STACK_BIAS) >
current->signal->rlim[RLIMIT_STACK].rlim_cur;
}

其中EXEC_STACK_BIAS是一个整型常量,定义如下:


#define EXEC_STACK_BIAS       (2*1024*1024)

很显然,CentOS把栈大小限制从soft limit往上提高了2MB,如果栈大小超过栈的soft limit+EXEC_STACK_BIAS(在我们这个例子中为12MB)则说明栈溢出。到此真相大白,把上面的测试代码修改一下(把数组大小改为12MB),再一运行进程果然崩溃。





3 小结



有时候接近问题的真相,但并没有发现问题的全部,就这个问题来说,如果我不写这篇文章,也就不会去写上面那段测试代码,就会想当然地认为在我的机器上栈的默认大小限制是10MB了。




本文链接

分享到:
评论

相关推荐

    堆栈溢出的解决方法

    什么是栈溢出及解决方案。

    EXCEL的VBA版本扫雷

    其实后来我又在这个基础上改进了一版功能更加强大的扫雷,解决了初版的一些BUG(比如递归深度过大造成的栈溢出) 但是弄丢啦 ε=(´ο`*))),手头只有这一个最终版001了,哪天找到了的话我再发上来吧。当然如果你要...

    快速排序.zip

    很好很强大的快速排序算法,无bug,可自测,而且对于几万个数字,不会有栈溢出的现象

    JSP 异常处理

    JSP 异常处理 当编写JSP程序的时候,程序员可能会遗漏一些BUG,这些BUG可能会出现在程序的任何地方。JSP代码中通常有以下几...举例来说,栈溢出错误。这些错误都会在编译期被忽略。 本节将会给出几个简单而优雅的方式

    基于递归和自定义堆栈的快速排序算法

    递归的缺点就是当排序数据量大时,系统堆栈会溢出 递归的实质是在堆栈中不断保存现场,但是现场的数据量是很大的 网上给出了堆栈实现的伪码算法,但是这里面存在很多的BUG 这个程序实现了用递归实现小量数据和用...

    01 基础.html

    js是什么:JavaScript是一个脚本语言 js的引入方式: 外部:&lt;script src=""&gt;&lt;/script&gt; 内部:&lt;script&gt;&lt;!--这里输入内容--&gt; 内嵌: &lt;!--组织a标签跳转--&gt; (0);"&gt; js的格式: 语句结束要有分号“;”(语句...

    C语言实战105例源码

    实例1 一个价值“三天”的BUG 2 实例2 灵活使用递增(递减)操作符 5 实例3 算术运算符计算器 7 实例4 逻辑运算符计算器 9 实例5 IP地址解析 11 实例6 用if…else语句解决奖金发放问题 13 实例7 用for...

    c语言实战105例源码

    1 一个价值“三天”的BUG  2 灵活使用递增(递减)操作符  3 算术运算符计算器  4 逻辑运算符计算器 5 IP地址解析  6 用if…else语句解决奖金发放问题  7 用for循环模拟自由落体  8 用while语句...

    C语言实战105例 含105个源代码

    实例1 一个价值“三天”的BUG 2 实例2 灵活使用递增(递减)操作符 5 实例3 算术运算符计算器 7 实例4 逻辑运算符计算器 9 实例5 IP地址解析 11 实例6 用if…else语句解决奖金发放问题 13 实例7 用for...

    Python灰帽子-黑客与逆向工程师的Python编程之道[简体中文版]

    10.4 构建一个驱动Fuzzer 第11章 IDAPython——IDA PRO环境下的Python脚本编程 11.1 安装IDAPython 11.2 IDAPython函数 11.2.1 两个工具函数 11.2.2 段(Segment) 11.2.3 函数 11.2.4 交叉引用 11.2.5 调试器钩子 ...

    《C语言实战105例》

    实例1 一个价值“三天”的BUG 2 实例2 灵活使用递增(递减)操作符 5 实例3 算术运算符计算器 7 实例4 逻辑运算符计算器 9 实例5 IP地址解析 11 实例6 用if…else语句解决奖金发放问题 13 实例7 用for...

    C语言实战105例源码.rar

    实例1 一个价值“三天”的BUG 2 实例2 灵活使用递增(递减)操作符 5 实例3 算术运算符计算器 7 实例4 逻辑运算符计算器 9 实例5 IP地址解析 11 实例6 用if…else语句解决奖金发放问题 13 实例7 用for...

    javalruleetcode-leetcode:leetcode

    java lru leetcode Problem ...多个容器同时标记/检查、9*9矩阵通过行号i和列号j计算3*3格子下标kk=i/3*3+j/3 Medium 回溯 Medium 回溯 Medium 字符串乘法 Medium 回溯、排列问题:thumbs_up: Mediu

    在一小时内学会 C#(txt版本)

    默认情况下存在一个全局命名空间,所以在命名空间外定义的类直接进到此全局命名空间中,因而你可以不用定界符访问此类。 你同样可以定义嵌套命名空间。 Using #include 指示符被后跟命名空间名的 using 关键字代替...

    c语言编写单片机技巧

    而汇编语言,一条指令就对应一个机器码,每一步执行什幺动作都很清楚,并且程序大小和堆栈调用情况都容易控制,调试起来也比较方便。所以在单片机开发中,我们还是建议采用汇编语言比较好。 如果对单片机C语言有...

Global site tag (gtag.js) - Google Analytics