缓冲溢出(相机卡缓冲区溢出)

1 引言“缓冲区溢出”对于现代操作系统和编译器来说已经不是什么大问题,但是作为一个合格的C/C++程序员,了解它的全部细节是绝对必要的。通常,计算机会使用一些内

缓冲溢出(相机卡缓冲区溢出)插图

1 引言

“缓冲区溢出”对于现代操作系统和编译器来说已经不是什么大问题,但是作为一个合格的C/C++程序员,了解它的全部细节是绝对必要的。

通常,计算机会使用一些内存,这些内存或者由程序内部使用,或者用于存储用户的输入数据。这种存储器通常被称为缓冲器。简单地说,缓冲区是一个连续的计算机内存区域,可以保存同一数据类型的多个实例,如字符数组。缓冲区溢出是指当计算机用数据位填充缓冲区时,超过了缓冲区本身的容量,溢出的数据覆盖了合法的数据。

2 C/C++中内存分配

任何源程序通常包括一个静态代码段(或文本段)和一个静态数据段。为了运行程序,操作系统首先负责为它创建一个进程,并在进程的虚拟地址空中映射它的代码段和数据段。但是,光有静态的代码段和数据段是不够的,流程在运行的过程中还要有它的动态环境。

一般来说,默认的动态存储环境是通过栈机制建立的。所有局部变量和所有按值传递的函数参数都是通过堆栈机制自动分配给memory 空的,分配给相同数据类型的相邻块的内存区域称为buffer。如下图。

缓冲溢出(相机卡缓冲区溢出)插图(1)

程序存储器中的映射

栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。全局区(静态区static):数据段,程序结束后由系统释放。全局区分为已初始化全局区(data),用来存放保存全局的和静态的已初始化变量和未初始化全局区(bss),用来保存全局的和静态的未初始化变量。常量区(文字常量区):数据段,存放常量字符串,程序结束后有系统释放。代码区:存放函数体(类成员函数和全局区)的二进制代码,这个段在内存中一般被标记为只读,任何对该区的写操作都会导致段错误(Segmentation Fault)。

需要注意的是,堆和栈是有区别的。很多程序员混淆了栈的概念,或者认为它们只是一个概念。简单来说,两者的主要区别可以表现在以下五个方面。

分配和管理方式不同

堆是动态分配的,其空的分配和释放由程序员控制。也就是说堆的大小不是固定的,而是可以动态扩展或收缩的,其分配是通过malloc()这样的实时内存分配函数来实现的。当进程调用malloc等函数分配内存时,新分配的内存被动态添加到堆中(堆被扩展);当使用free和其他函数释放内存时,释放的内存从堆中移除(堆减少)。

堆栈由编译器自动管理,分配方式有两种:静态分配和动态分配。静态分配是由编译器完成的,比如局部变量的分配。动态分配是由alloca()函数分配的,但是栈的动态分配不同于堆,它的动态分配是由编译器释放的,不需要人工控制。

申请的大小限制不同

栈是扩展到低位地址的数据结构,是一个连续的内存区域。栈顶地址和栈最大容量由系统预先确定,栈中可用的空较小。

堆是扩展到高位地址的数据结构,是不连续的内存区域。这是因为系统通过链表存储空空闲内存地址,自然堆是不连续的内存区域。而且链表的遍历也是从低位地址到高位地址,堆大小受限于计算机系统的有效虚拟内存空。

由此空,堆得到的空更灵活,更大。32位平台下,VC6下默认值1M,最大堆可达4g;

申请效率不同栈由系统自动分配,速度快,但是程序员无法控制。堆是有程序员自己分配,速度较慢,容易产生碎片,不过用起来方便。产生碎片不同

对于堆来说,频繁的malloc或者自由执行必然会导致内存空的不连续,形成大量的碎片,降低程序的效率。对于栈来说,不存在碎片问题。

内存地址增长的方向不同堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长;栈的增长方向与之相反,是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长。

假设一个程序的函数调用顺序是:主函数调用func1,func1调用func2。当这个程序被操作系统调入内存时,其对应进程在内存中的映射结果如下图所示。

缓冲溢出(相机卡缓冲区溢出)插图(2)

示例中的存储器映射

进程的堆栈由多个堆栈帧组成,每个堆栈帧对应一个函数调用。当函数被调用时,新的堆栈帧被推入堆栈;当函数返回时,相应的堆栈框架从堆栈中弹出。由于函数返回地址等重要数据需要存储在程序员可见的堆栈中,这也给系统安全带来了极大的隐患。

当程序写入超出缓冲区边界时,就会发生所谓的“缓冲区溢出”。当缓冲区溢出时,会覆盖下一个相邻的内存块,导致程序出现一些意想不到的结果:也许程序还能继续,也许程序执行中出现了奇怪的现象,也许程序完全失败或者崩溃等等。

缓冲区溢出

一般来说,缓冲区溢出可以分为四种类型:堆栈溢出、堆溢出、BSS溢出和格式字符串溢出。其中,堆栈溢出是最简单也是最常见的溢出方式。

没有保证足够的存储空间存储复制过来的数据

void function(char * str){ char buffer[10];strcpy(缓冲区,str);}上面的strcpy()会直接将str的内容复制到buffer中。只要str的长度大于10,就会溢出缓冲区,导致程序运行错误。像strcpy这样有问题的标准函数有strcat()、sprintf()、vsprintf()、gets()、scanf()等等。还有更安全的函数对应,就是在函数名后面加_s,比如scanf_s()函数。

严格检查输入长度和缓冲区长度。常见的高危函数

Get(),一种防止函数严重的手段,是最危险的。使用fgets(buf,size,stdin) strncpy() strcat()是危险的使用strncat() sprintf()是危险的使用snprintf()是危险的,或者使用精度说明符scanf()是危险的,或者使用精度说明符进行自解析sscanf()是危险的,或者使用精度说明符进行自解析fscanf()是危险的,或者使用精度说明符进行自解析vfscanf()是危险的,或者使用vsnprintf()而不是vfscanf()是危险的, 或者使用精度说明符vscanf()是危险的,或者对vsscanf()使用精度说明符是危险的,或者对streadd()使用精度说明符是危险的,或者对自己的分析使用精度说明符是危险的。

整数溢出宽度溢出:把一个宽度较大的操作数赋给宽度较小的操作数,就有可能发生数据截断或符号位丢失

# include & ltstdio.h & gtint main(){ signed value 1 = 10;usignedintvalue 2 =(unsignedint)value 1;}算术溢出。即使程序在接受用户输入时对A和B的赋值进行安全检查,a+b仍然可能溢出:# include < stdio.h & gtint main(){ inta;intbintc = a * b;return0}数组索引不在合法的枚举范围{TABLESIZE=100}内;int * table = NULLintinsert_in_table(intpos,intvalue){if(!table){ table =(int *)malloc(sizeof(int)* TABLESIZE);} if(pos & gt;= TABLESIZE){ return-1;} table[pos]= value;return0}其中:pos为int类型,可能为负,会导致写入数组引用的内存边界之外。您可以将pos类型更改为size_t来避免它。

空字符错误

例如:

//错误chararray[]={'0','1','2','3','4','5','6','7','8'};//正确的写法应为:chararray[]={'0','1','2','3','4','5','6','7','8',’\0’};//或者chararray[11]={'0','1','2','3','4','5','6','7','8','9’};

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

作者:美站资讯,如若转载,请注明出处:https://www.meizw.com/n/111968.html

发表回复

登录后才能评论